求两个正整数的最大公约数Greatest Common Divisor,GCD。
如果两个正整数都很大,有什么简单的算法?
例如求1 100 100 210 001 和 120 200 021 的最大公约数
解法一:
辗转相除法
假设用f(x,y)表示x,y的最大公约数,取k = x/y, b = x%y, 则x = ky + b, 如果一个数能够同时整除x和y,则必能同时整除b和y,而能同时整除b和y的数也必能同时整除x和y,即x和y的公约数与b和y的公约数是相同的,其最大公约数也是相同的。
则有f(x,y) = f(y, x%y) (x >= y > 0)
1 | int gcd(int x, int y) |
解法二:
对于大整数而言,取模运算(其中用到除法)是非常昂贵的开销,有没有办法能不用取模运算?
采用类似前面辗转相除法的分析,如果一个数能够同时整除x和y,则必能同时整除x-y和y;而能够同时整除x-y和y的数也必能同时整除x和y,即x和y的公约数与x-y和y的公约数是相同的,其最大公约数也是相同的,即f(x,y) = f(x-y, y), 那么就可以不再需要进行大整数的取模运算,而转换成简单得多的大整数的减法。
在实际操作中,如果x < y, 可以先交换(x, y),从而避免求一个正数和一个负数的最大公约数,一直迭代下去,知道其中一个数为0
1 | BigInt gcd(BigInt x, BigInt y) |
代码中BigInt是自己实现的一个大整数类。
这个算法免去了大整数触发的繁琐,但是同样有不足之处,最大的瓶颈就是迭代的次数比之前多了很多。
解法三:
能否结合解法一和解法二,得到一个更佳的算法
对于y 和x 来说, 如果y = k*y1, x = k*x1。 那么有f(y, x) = k*f(y1, x1)。
另外,如果x = p x1, 假设p是素数,并且y%p!=0(即y不能被p整除),那么f(x, y) = f(px1, y) = f(x1, y)
因此:最简单的方法是,2是一个素数,同时对于二进制表示的大整数而言,可以很容易地将除以2和乘以2地运算转化为移位运算,从而避免大整数触发,由此就可以利用2来进行分析。
1 | BigInt gcd(BigInt x, BigInt y) |