• 求最大公约数


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

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

    也就是最大公约数就是相同的商之间相乘得到的数,而最大公约数的英文表示(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,然后整个递归结束,最大公约数找到!!

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

  • 相关阅读:
    医院信息化管理系统(HIS)与医院网络时钟系统
    实验吧—Web——WP之 上传绕过
    实验吧—Web——WP之 猫抓老鼠
    实验吧—Web——WP之 因缺思汀的绕过
    实验吧—Web——WP之 貌似有点难
    实验吧—Web——WP之 Forms
    实验吧—隐写术——WP之 我喜欢培根
    实验吧—隐写术——WP之 Fair-Play
    实验吧—隐写术——WP之 奇妙的音乐
    实验吧—隐写术——WP之 男神一般都很低调很低调的!!
  • 原文地址:https://www.cnblogs.com/webor2006/p/7196255.html
Copyright © 2020-2023  润新知