大四毕业前夕,计算机学院的小灰又一次顶着炎炎烈日,
去某IT公司面试研发工程师岗位......
![](https://pic1.zhimg.com/80/v2-623cb518b8783a7092c61fa0d3326808_720w.jpg)
半小时后,公司会议室,面试开始......
![](https://pic3.zhimg.com/80/v2-492686419624e1929659a2dafbef9c52_720w.jpg)
![](https://pic3.zhimg.com/80/v2-fdebda39c5058da3e80b7484e7a1acd2_720w.jpg)
![](https://pic4.zhimg.com/80/v2-546b91843fea15b91482275de79e14c7_720w.jpg)
![](https://pic2.zhimg.com/80/v2-9151a309f6705609b41410372ba58b39_720w.jpg)
![](https://pic3.zhimg.com/80/v2-3b718dbd6b2a3da98e6464562736b786_720w.jpg)
![](https://pic3.zhimg.com/80/v2-d9b86ee9ef414baf4510f60d149cc67e_720w.jpg)
![](https://pic1.zhimg.com/80/v2-da65dd1235684beaf5e68de912e3a21c_720w.jpg)
小灰奋笔疾书,五分钟后......
![](https://pic4.zhimg.com/80/v2-bda38a6c8bca166c42a0f723fe816bb3_720w.jpg)
![](https://pic1.zhimg.com/80/v2-1697d6fe277c21c540f3b455301fa7a8_720w.jpg)
小灰的思路十分简单。他使用暴力枚举的方法,试图寻找到一个合适的整数 i,看看这个整数能否被两个整型参数numberA和numberB同时整除。
这个整数 i 从2开始循环累加,一直累加到numberA和numberB中较小参数的一半为止。循环结束后,上一次寻找到的能够被两数整除的最大 i 值,就是两数的最大公约数。
![](https://pic3.zhimg.com/80/v2-6d39881b000f7a72b49badfc445ca0aa_720w.jpg)
![](https://pic1.zhimg.com/80/v2-0c1438b0fcec3421c03ae316899f13e0_720w.jpg)
![](https://pic1.zhimg.com/80/v2-da65dd1235684beaf5e68de912e3a21c_720w.jpg)
![](https://pic4.zhimg.com/80/v2-0230f3fef9319cdae3f2bb10f0e67483_720w.jpg)
![](https://pic2.zhimg.com/80/v2-708c345f788f33671768f48692ab3455_720w.jpg)
![](https://pic2.zhimg.com/80/v2-1676dda6dff997e51a0042d83e1cfb5d_720w.jpg)
事后,垂头丧气的小灰去请教同系的学霸大黄......
![](https://pic4.zhimg.com/80/v2-8223b169ec6222875b4231435c2e7df3_720w.jpg)
![](https://pic2.zhimg.com/80/v2-70da32fd57bf8f25ae25c5680cb6e6d5_720w.jpg)
![](https://pic4.zhimg.com/80/v2-a0122631a11a50fba3e38550d5864b93_720w.jpg)
辗转相除法, 又名欧几里得算法(Euclidean algorithm),目的是求出两个正整数的最大公约数。它是已知最古老的算法, 其可追溯至公元前300年前。
这条算法基于一个定理:两个正整数a和b(a>b),它们的最大公约数等于a除以b的余数c和b之间的最大公约数。比如10和25,25除以10商2余5,那么10和25的最大公约数,等同于10和5的最大公约数。
![](https://pic2.zhimg.com/80/v2-3962a41f1319a572918602f7d37c9665_720w.jpg)
有了这条定理,求出最大公约数就简单了。我们可以使用递归的方法来把问题逐步简化。
首先,我们先计算出a除以b的余数c,把问题转化成求出b和c的最大公约数;然后计算出b除以c的余数d,把问题转化成求出c和d的最大公约数;再然后计算出c除以d的余数e,把问题转化成求出d和e的最大公约数......
以此类推,逐渐把两个较大整数之间的运算简化成两个较小整数之间的运算,直到两个数可以整除,或者其中一个数减小到1为止。
![](https://pic1.zhimg.com/80/v2-52c4aac94920b96fc7cff644b3f3c8d4_720w.jpg)
![](https://pic3.zhimg.com/80/v2-2cfeeab2e4e6f8362bb345b11fb4ea6e_720w.jpg)
五分钟后,小灰改好了代码......
![](https://pic4.zhimg.com/80/v2-cd6da92cc0b2c267e302ecf5948c1197_720w.jpg)
![](https://pic2.zhimg.com/80/v2-f27a3fe33735251bf906b062bc9d78d5_720w.jpg)
![](https://pic2.zhimg.com/80/v2-9350ab8bf2a1d8783af59ce3925209a1_720w.jpg)
![](https://pic3.zhimg.com/80/v2-9e0eeb197c8a290909f97d88e395eb5e_720w.jpg)
更相减损术, 出自于中国古代的《九章算术》,也是一种求最大公约数的算法。
他的原理更加简单:两个正整数a和b(a>b),它们的最大公约数等于a-b的差值c和较小数b的最大公约数。比如10和25,25减去10的差是15,那么10和25的最大公约数,等同于10和15的最大公约数。
![](https://pic4.zhimg.com/80/v2-0a5816f69eb60a9b832838adfebb239f_720w.jpg)
由此,我们同样可以通过递归来简化问题。首先,我们先计算出a和b的差值c(假设a>b),把问题转化成求出b和c的最大公约数;然后计算出c和b的差值d(假设c>b),把问题转化成求出b和d的最大公约数;再然后计算出b和d的差值e(假设b>d),把问题转化成求出d和e的最大公约数......
以此类推,逐渐把两个较大整数之间的运算简化成两个较小整数之间的运算,直到两个数可以相等为止,最大公约数就是最终相等的两个数。
![](https://pic2.zhimg.com/80/v2-ceef2fc424c54eacbd8bd6b278cb2569_720w.jpg)
![](https://pic3.zhimg.com/80/v2-2cfeeab2e4e6f8362bb345b11fb4ea6e_720w.jpg)
五分钟后,小灰重写了代码......
![](https://pic1.zhimg.com/80/v2-af7556bbef6cb943b2908142084f4b48_720w.jpg)
![](https://pic3.zhimg.com/80/v2-1c91ff059474fa6261c7aa0257265e46_720w.jpg)
![](https://pic1.zhimg.com/80/v2-3f13c599f6c8c9cfe51bc80d4a20e4ec_720w.jpg)
![](https://pic2.zhimg.com/80/v2-f8cd2b2ffe3f24b6b9b446c960b21315_720w.jpg)
![](https://pic1.zhimg.com/80/v2-44e1cd14959d06946cda15a5c5bee738_720w.jpg)
![](https://pic4.zhimg.com/80/v2-a8812f5d2511ad4b287a09a97a9bdd03_720w.jpg)
众所周知,移位运算的性能非常快。对于给定的正整数a和b,不难得到如下的结论。其中gcb(a,b)的意思是a,b的最大公约数函数:
当a和b均为偶数,gcb(a,b) = 2*gcb(a/2, b/2) = 2*gcb(a>>1, b>>1)
当a为偶数,b为奇数,gcb(a,b) = gcb(a/2, b) = gcb(a>>1, b)
当a为奇数,b为偶数,gcb(a,b) = gcb(a, b/2) = gcb(a, b>>1)
当a和b均为奇数,利用更相减损术运算一次,gcb(a,b) = gcb(b, a-b), 此时a-b必然是偶数,又可以继续进行移位运算。
比如计算10和25的最大公约数的步骤如下:
- 整数10通过移位,可以转换成求5和25的最大公约数
- 利用更相减损法,计算出25-5=20,转换成求5和20的最大公约数
- 整数20通过移位,可以转换成求5和10的最大公约数
- 整数10通过移位,可以转换成求5和5的最大公约数
- 利用更相减损法,因为两数相等,所以最大公约数是5
在两数比较小的时候,暂时看不出计算次数的优势,当两数越大,计算次数的节省就越明显。
![](https://pic1.zhimg.com/80/v2-0a68340b980b511fd2e682432bf9016c_720w.jpg)
![](https://pic2.zhimg.com/80/v2-903da760b0096b5e6a1bb1e73582309d_720w.jpg)
最后总结一下上述所有解法的时间复杂度:
1.暴力枚举法:时间复杂度是O(min(a, b)))
2.辗转相除法:时间复杂度不太好计算,可以近似为O(log(max(a, b))),但是取模运算性能较差。
3.更相减损术:避免了取模运算,但是算法性能不稳定,最坏时间复杂度为O(max(a, b)))
4.更相减损术与移位结合:不但避免了取模运算,而且算法性能稳定,时间复杂度为O(log(max(a, b)))
![](https://pic3.zhimg.com/80/v2-77cca81f8632c51476e885e5c261b2ae_720w.jpg)
![](https://pic4.zhimg.com/80/v2-a9d14fd4ff87f2de4724fad2239d4c9f_720w.jpg)
本文原本只写到辗转相除法就终告结束,后来网友们指出还有更优化的解法,看来自己还是才疏学浅,很感谢大家指出问题。另外,方法的参数默认必定是正整数,所以在代码中省去了合法性检查。
文中描述的更相减损术是简化了的方式。在九章算术原文中多了一步验证:如果两数都是偶数,计算差值之前会首先让两个数都折半,使得计算次数更少。这种方法做到了部分优化,但古人似乎没想到一奇一偶的情况也是可以优化的。
由于篇幅所限,本文省略了关于辗转相除法原和更相减损术的原理及证明。其实证明过程并不复杂,细心的同学们也可以自己尝试研究一下。谢谢大家的捧场!