神马是最大公约数呢,反正我这学渣是已经忘着差不多了,借此来复习一下:
上面文字看完是不是还一脸抽象,下面看个图立马就能秒懂:
也就是最大公约数就是相同的商之间相乘得到的数,而最大公约数的英文表示(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算法【辗转相除法】的取余数过程。
二进制最大公约数基于下述思路:
- 若a、b都是偶数,则gcd(a,b)=2*gcd(a/2,b/2),因为偶数的最大公约数肯定是偶数,所以用这种相除的办法很快就能求解出来,如:gcd(12,6) = 2 * gcd(6,3),这时发现a=6是偶数,b=3是奇数,则执行
- 如果有一个是奇数,有一个是偶数则按如下规则:若a是奇数、b是偶数,则gcd(a,b)=gcd(a,b/2);若a是偶数、b是奇数,则gcd(a,b)=gcd(a/2,b),因为它们的公约数不可能是偶数,所以将偶数直接除成奇数继续收敛。
- 若a、b都是奇数,a > b ? gcd(a,b)=gcd((a-b)/2,b) : gcd(a,b)=gcd((b-a)/2,a),而奇数减奇数等于偶数,所以又会成为第二种情况,则继续按第二种情况递归既可。
- 最后如果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,然后整个递归结束,最大公约数找到!!
上面是用三种方法来实现最大公约数的求解的完整过程,不断优化也是未来要追求的,在面试中面试官可能也会根据你的实现然后不断的让你进行优化,所以实现是一方面,怎么去优化它也很重要~另外这节中比较啰嗦,可能有些特别特别基础的概念不值得一得,但是对我来说彻底理解才是重点,不管有多白痴的小细节我也会记录下来,因为有助于我的理解。