辗转相除法(欧几里得算法)
欧几里德算法又称辗转相除法,是指用于计算两个正整数a,b的最大公约数。
时间复杂度为(O(logN))。
举例
比如:30和42的最大公约数:
(30 mod 42 = 30)
(42 mod 30 = 12)
(30 mod 12 = 6)
(12 mod 6 = 0)
那么,30和42的最大公约数就是6。
代码实现
int gcd(int a, int b) {
while (b > 0) {
c = a % b;
a = b;
b = c;
}
return a;
}
我们也可以使用递归的方法来实现。
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
性能分析
由于辗转相除法的时间复杂度为(O(logN)),在遇到比较大的数时((Nge{10^{256}})),计算起来就会比较慢了。
这是因为辗转相除法中含有模运算,当(amod{b})时,相当于(a)减了(k)次(b),也就是(a - k * b)。
比如:
(42 mod 30 = 42 - 1 * 30)
(30 mod 12 = 30 - 2 * 12)
更相减损术
更相减损术是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。
最坏时间复杂度为(O(N))。
举例
当(a>b)时,用(a-b);当(b>a)时,用(b-a)。
(gcd(30, 42) = gcd(12, 30) = gcd(18, 12) = gcd(6, 12) = gcd(6, 6))
当(a=b)时,(a)就是这两个数的最大公约数。
代码实现
int gcd(int a, int b) {
if (a == b) return a;
else if (a > b) return gcd(a - b, b);
else if (a < b) return gcd(b - a, a);
}
更相减损术二分版
上文提到,在两个差距非常大的数((a=10000, b=1)),使用更相减损术的时间复杂度为(O(N))。
这个算法的时间复杂度为(O(logN))。
代码实现
可以使用按位与(&)来代替模运算。
例如:((5)_{10} = (101)_{2}),我们可以使用5&1
来达到和模运算符一样的效果。
我们知道,按位左移1位就是乘2,右移一位就是除以2。
int gcd(int a, int b) {
if (a == b) return a;
if ((a & 1) && (b & 1)) return gcd(a >> 1, b >> 1) << 2; // a和b都是偶数
else if ((a & 1) && !(b & 1)) return gcd(a >> 1, b); // a是偶数b是奇数
else if (!(a & 1) && (b & 1)) return gcd(a, b >> 1); // a是奇数b是偶数
else if (!(a & 1) && !(b & 1)) { // a和b都是奇数
if (a > b) return gcd(a - b, b);
else if (a < b) return gcd(b - a, a);
}
}
参考
感谢Vita小老师~
【算法小知识】如何求最大公约数(上)
【算法小知识】如何求最大公约数(下)