最大公约数算法

775 阅读4分钟

辗转相除法

欧几里得算法,又名辗转相除法,是求最大公约数的算法。两个整数的最大公约数是能够同时整除它们的最大的正整数。辗转相除法基于如下原理:两个整数的最大公约数等于其中较小的数和两数的差的最大公约数。例如,252和105的最大公约数是21(252 = 21 × 12;105 = 21 × 5);因为252 − 105 = 147,所以147和105的最大公约数也是21。在这个过程中,较大的数缩小了,所以继续进行同样的计算可以不断缩小这两个数直至其中一个变成零。这时,所剩下的还没有变成零的数就是两数的最大公约数。 辗转相除法的演示动画 参考辗转相除法的演示动画:两条线段分别表示252和105,其中每一段表示21。动画演示了循环从大数中减去小数,直到其中一段的长度为0,此时剩下的一条线段的长度就是252和105的最大公约数。

def gcd_euclid(a, b):
    if a == b:
        return a
    big = a if (a > b) else b
    small = a if (a < b) else b
    mod = big % small
    if mod == 0:
        return small
    return gcd_euclid(small, mod)
print(gcd_euclid(1023243, 3435))
print(gcd_euclid(24, 9))
print(gcd_euclid(39079964, 318657888885))

辗转相除法最大的缺点是存在大量的取模计算。

更相减损术

《九章算术》是中国古代的数学专著,其中的“更相减损术”可以用来求两个数的最大公约数,原文是:

可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。

白话文译文:(如果需要对分数进行约分,那么)可以折半的话,就折半(也就是用2来约分)。如果不可以折半的话,那么就比较分母和分子的大小,用大数减去小数,互相减来减去,一直到减数与差相等为止,用这个相等的数字来约分。

def gcd_decrease(a, b):
    if a == b:
        return a
    big = a if (a > b) else b
    small = a if (a < b) else b
    return gcd_decrease(small, big - small)
print(gcd_decrease(24, 9))
print(gcd_decrease(1023243, 3435))
print(gcd_decrease(39079964, 318657888885))

上述测试第三行可能就会出现堆栈溢出了 修改为循环算法:

def gcd_decrease(a, b):
    big = a if (a > b) else b
    small = a if (a < b) else b
    while big != small:
        big = big - small
        if big < small:
            big, small = small, big
    return small
print(gcd_decrease(24, 9))
print(gcd_decrease(1023243, 3435))
print(gcd_decrease(39079964, 318657888885))

更相减损术免除了辗转相除法的取模计算,但是两数差值较大时,将会出现很多次的调用,性能退化严重,比如求1000000000和99999999的最大公约数。

Stein算法

Stein算法由J. Stein 1961年提出,这个方法也是计算两个数的最大公约数。和欧几里德算法 算法不同的是,Stein算法只有整数的移位和加减法,因此对于大素数Stein将更有优势。

算法思路:

1.当a和b均为偶数时,gcd(a,b) = 2×gcd(a/2, b/2)= 2×gcd(a>>1,b>>1)
2.当a为偶数,b为奇数时,gcd(a,b) = gcd(a/2,b) = gcd(a>>1,b)
3.当a为奇数,b为偶数时,gcd(a,b) = gcd(a,b/2) = gcd(a,b>>1)
4.当a和b均为奇数时,先利用更相减损术运算一次,gcd(a,b) = gcd(b,a-b),此时a-b必然是偶数,然后又可以继续进行移位运算。

实现:

def gcd_stein(a, b):
    if a == b:
        return a
    elif (a & 1) and (b & 1):
        if a < b:
            a, b = b, a
        return gcd_stein(b, a - b)
    elif (a & 1) and not (b & 1):
        return gcd_stein(a, b >> 1)
    elif not (a & 1) and (b & 1):
        return gcd_stein(a >> 1, b)
    else:
        return gcd_stein(a >> 1, b >> 1) << 1
print(gcd_stein(24, 9))
print(gcd_stein(1023243, 3435))
print(gcd_stein(39079964, 318657888885))

Stein算法在消除取模运算的基础上尽可能减少了更相减损法的运算次数。

延伸-最小公倍数算法

在已经算出整数a、b的最大公约数的基础上,我们可以通过下面的公式来求出它们的最小公倍数

lcm(a, b) = (a * b)/gcd(a, b)