求最大公约数

神马是最大公约数呢,反正我这学渣是已经忘着差不多了,借此来复习一下:

上面文字看完是不是还一脸抽象,下面看个图立马就能秒懂:

也就是最大公约数就是相同的商之间相乘得到的数,而最大公约数的英文表示(greatest common divisor,简写为gcd),而求公约数有很多方法,这里学习“辗转相除法”、“更相减损法”、“二进制求解法”这三种解法,下面燥起来吧!

辗转相除法:

先来看一下它的定义,下面会用程序来论证它:

再来看一下它的具体实现:

编译运行:

下面用debug方式来分析一下它的流程,看是否跟上面原理图上分析的是一样的:

其中参数:m=319;n=377:

①、n == 0为假,则执行②;

②、gcd_mod(377, 319 % 377) = gcd_mode(377, 319);

其中参数:m=377;n=319:

①、n == 0为假,则执行②

②、gcd_mod(319, 377 % 319) = gcd_mode(319, 58);

其中参数:m=319;n=58:

①、n == 0为假,则执行②

②、gcd_mod(58, 319 % 58) = gcd_mode(58, 29);

其中参数:m=58;n=29:

①、n == 0为假,则执行②

②、gcd_mod(29, 58 % 29) = gcd_mode(29, 0);

其中参数:m=29;n=0:

①、n == 0为真,则直接返回m,也就是最终的结果是29~ 

这种实现方式可以看到其收敛速度非常快,倍数级的,所以效率是比较高的,但是!!!由于“%”这个操作还是稍重一些,有木有更优的方案来实现呢?当然有,继续看!

更相减损法【更优的方式】:

先来看一下它的定义:

再来看一下它的具体实现: 

编译运行:

下面debug一下流程:

 

其参数:m=98;n=63

①、m == n 为 false;n == 0 为 false,整个条件为假执行条件②;

②、m == 0 为 false,整个条件为假执行条件③;

③、m > n 条件 true,则执行条件体:gcd_sub(63, 35);

其参数:m=63;n=35

①、m == n 为 false;n == 0 为 false,整个条件为假执行条件②;

②、m == 0 为 false,整个条件为假执行条件③;

③、m > n 条件 true,则执行条件体:gcd_sub(35, 28);

其参数:m=35;n=28

①、m == n 为 false;n == 0 为 false,整个条件为假执行条件②;

②、m == 0 为 false,整个条件为假执行条件③;

③、m > n 条件 true,则执行条件体:gcd_sub(28, 7);

其参数:m=28;n=7

①、m == n 为 false;n == 0 为 false,整个条件为假执行条件②;

②、m == 0 为 false,整个条件为假执行条件③;

③、m > n 条件 true,则执行条件体:gcd_sub(7, 21);

其参数:m=7;n=21

①、m == n 为 false;n == 0 为 false,整个条件为假执行条件②;

②、m == 0 为 false,整个条件为假执行条件③;

③、m > n 条件 false,整个条件为假执行条件④;

④、gcd_sub(7, 14);

其参数:m=7;n=14

①、m == n 为 false;n == 0 为 false,整个条件为假执行条件②;

②、m == 0 为 false,整个条件为假执行条件③;

③、m > n 条件 false,整个条件为假执行条件④;

④、gcd_sub(7, 7);

其参数:m=7;n=7

①、m == n 为 true;n == 0 为 false,整个条件为true整个递归结束返回;

由于取模运算在计算机上是一个比较复杂的工作,相比之下减法运算要高效很多,于是乎第一个优化方案产生,那!!!还有没有更高效的呢?继续往下

二进制求解法【更更优的方式】:

先来看一下它的定义:

求最大公约数的Euclid算法辗转相除法需要用到大量的取模运算,这在大多数计算机上是一项复杂的工作,相比之下减法运算、测试数的奇偶性、折半运算的执行速度都要更快些。

二进制最大公约数算法避免了Euclid算法辗转相除法的取余数过程。

二进制最大公约数基于下述思路:

  1. 若a、b都是偶数,则gcd(a,b)=2*gcd(a/2,b/2),因为偶数的最大公约数肯定是偶数,所以用这种相除的办法很快就能求解出来,如:gcd(12,6) = 2 * gcd(6,3),这时发现a=6是偶数,b=3是奇数,则执行
  2. 如果有一个是奇数,有一个是偶数则按如下规则:若a是奇数、b是偶数,则gcd(a,b)=gcd(a,b/2);若a是偶数、b是奇数,则gcd(a,b)=gcd(a/2,b),因为它们的公约数不可能是偶数,所以将偶数直接除成奇数继续收敛。
  3. 若a、b都是奇数,a > b ? gcd(a,b)=gcd((a-b)/2,b) : gcd(a,b)=gcd((b-a)/2,a),而奇数减奇数等于偶数,所以又会成为第二种情况,则继续按第二种情况递归既可。
  4. 最后如果a=b,那递归直接返回;如果a=0,则返回b;如果b=0,则返回a。

有了上面的思路之后,在正式代码实现之前还需要明白几个知识点,下面一一来做实验来说明下:

①、一个数如何判断它是偶数和奇数呢?我想第一时间能想到的就是用数除以2,如果余数为0则证明该数是偶数,为1则为奇数;但是!除在计算机中算是一个比较复杂的操作,有木有更加高效的办法能判断出来呢,当然有!也就是要将其特殊说明的:利用位运算,这个相比除来效率要高很多,那怎么来利用位运算来判断呢?下面先把结果说出来,之后再来根据结果来分析为啥具体原因,如下:

假设有个数为n,那么:

if (~n & 1) {

  //说明是偶数

if (n & 1) {

  //说明是奇数

“耳听为虚,眼见为实”,下面用程序论证一下:

编译运行:

为什么这样用位操作就能判断呢?下面拿“2”这个数来分析一下:
因为位操作得将2换成二进制,而它换成二进制为:00000010;而1的二进制为:00000001

其中判断偶数的条件是:“~m & 1”,而计算如下:
~2--->11111101

&

 1---->00000001

==============

     00000001

而“00000001”换成bool也就是为true,所以说2这个数为偶数,下面换一个奇数“3”以同样的判断条件看一下,3的二进制为:00000011:

~3--->11111100

&

 1---->00000001

==============

     00000000

而“00000000”换成bool也就是为false,所以说3这个数为奇数

也就是只要判断最后一位是否为1,如果为1则表示是偶数,如果为0则为奇数。

同理判断奇数"m & 1"也是根据奇数最后一位是以1结尾,偶数最后一位是以0结尾为原则来计算的,所以就很容易理解了。

②、在上面原理实现中有说到除相关的操作,回顾下:

而除在计算机中是已经复杂的,更高效的方式当然还是转成位操作,那用位操作如何才能表示除2的效果呢?依然直接给出结果:
假设有个数为n,那么:n >> 1 = n / 2,下面用代码论证下:

编译运行:

为什么呢?下面换成二进制来分析一下:
"6"换算成二进制为00000110

>>1之后二进制为  00000011

======================

结果换算成十进制结果就是3

③、而乘在计算机中也是比较复杂的,而原理中也提到了一个乘相关的:

而高效的做法当然也跟②一样改用移位操作啦,很容易理解一个数n * 2 = n << 1,如下:

懂了上面的东东之后,下面就正式开始实现高大上的解法啦,也比较容易懂了:

编译运行:

下面继续对上面程序debug一下:

其参数m = 319、n = 377

①、,条件为假,继续往下执行。

②、,条件为假,继续往下执行。

③、,条件为假,继续往下执行。

④、,其中~m & 1为假,也就是不是偶数,条件不满足,继续往下执行。

⑤、,其中n为奇数,所以条件不满足,继续往下执行。

 这时当m,n两数全为奇数的时候,则执行下面两个条件:

⑥、,m > n的条件为假,所以继续往下执行。 

⑦、,gcd_binary(58 >> 1, 319) = gcd_binary(29, 319),继续递归

Loop1:

其参数m = 29、n = 319

①、,条件为假,继续往下执行。

②、,条件为假,继续往下执行。

③、,条件为假,继续往下执行。

④、,其中~m & 1为假,也就是不是偶数,条件不满足,继续往下执行。

⑤、,其中n为奇数,所以条件不满足,继续往下执行。

 这时当m,n两数全为奇数的时候,则执行下面两个条件:

⑥、,m > n的条件为假,所以继续往下执行。 

⑦、,gcd_binary(290 >> 1, 29) = gcd_binary(145, 29),继续递归

Loop2:

其参数m = 145、n = 29

①、,条件为假,继续往下执行。

②、,条件为假,继续往下执行。

③、,条件为假,继续往下执行。

④、,其中~m & 1为假,也就是不是偶数,条件不满足,继续往下执行。

⑤、,其中n为奇数,所以条件不满足,继续往下执行。

 这时当m,n两数全为奇数的时候,则执行下面两个条件:

⑥、,m > n的条件为真,执行条件体:gcd_binaray(116 >> 1, 29) = gcd_binaray(58, 29),继续递归。

Loop3:

其参数m = 58、n = 29

①、,条件为假,继续往下执行。

②、,条件为假,继续往下执行。

③、,条件为假,继续往下执行。

④、,其中~m & 1为true,也就是偶数,条件满足,执行条件体:
  a、n为奇数,所以 n & 1条件满足,执行条件体:gcd_binary(58 >> 1, 29) = gcd_binary(29, 29),继续递归: 

Loop4:

其参数m = 29、n = 29

①、,条件为真,直接返回29,然后整个递归结束,最大公约数找到!!

上面是用三种方法来实现最大公约数的求解的完整过程,不断优化也是未来要追求的,在面试中面试官可能也会根据你的实现然后不断的让你进行优化,所以实现是一方面,怎么去优化它也很重要~另外这节中比较啰嗦,可能有些特别特别基础的概念不值得一得,但是对我来说彻底理解才是重点,不管有多白痴的小细节我也会记录下来,因为有助于我的理解。

原文地址:https://www.cnblogs.com/webor2006/p/7196255.html