前两天在看书时,遇到一个求最大公约数的程序题,我思索良久,居然想不起来该如何解答。

那一瞬间我仿佛看到了我的小学数学老师手拿三角板正在怒气冲冲的看着我,责问我为什么这么简单的数学题都不会。于是我就厚着脸皮再次重温了一下小学的数学知识,以下是记录的笔记。

短除法

短除法是求最大公约数最为简单的方法之一,还依稀记得我小学的数学老师教的就是这一种。

算法定义

不断除以两数的公因数,直到两数不再有公因数,把前面所有的除数相乘,即为两数的最大公约数。

算法示例

计算42,70 的最大公约数

设 a = 42,b = 70
可以看到两者有公因数2,那么两数同时除以2
a = 42 / 2 = 21
b = 70/ 2 = 35
可以看到两者还有公因数7,那么两数同时除以7
a = 21 / 7 = 3
b = 35 / 7 = 5
此时两数已经没有公因数了
所以 42,70 的最大公约数 2 * 7 = 14

算法小结

短除法使用简单,但对于质因数过大的数则会很吃力,不信你用短除法算下 12759,21265 的最大公约数试试。

质因数分解法

质因数分解法使用简单,和短除法一样,也是学习求最大公约数的入门级算法之一。

算法定义

对两个数进行质因数分解,然后提取两个因式中共同的质因数相乘,相乘的积,即为两数的最大公约数。

算法示例

计算42,70 的最大公约数

设 a = 42,b = 70
对两数进行质因式分解
a = 2 * 3 * 7
b = 2 * 5 * 7
可以看到两数共同的质公因数为2,7
所以 42,70 的最大公约数 2 * 7 = 14

算法小结

质因数分解法和短除法原理基本类似,虽然使用简单,但对于质因数过大的数仍然无能为力。

枚举法

枚举法简而言之就是尝试每一种可能性,其实上面质因数分解法和短除法就包含枚举法的思想,只是一般用人脑来完成。因此也就出现了对质因数过大的数无能为力的情况,但如果把该解法用计算机来实现就会轻松许多。

算法定义

从小到大不断尝试两数的公因数,如果尝试成功,则两数同时除以尝试成功的数。

然后以当前尝试成功的数做为最小数再进行尝试,依次反复,直到尝试的数大于其中一个数,把所有尝试成功的数相乘,即为两数的最大公约数。

编程实现

def gcd(a, b):
	# 最大公因数初始值
	cd = 1
	# 尝试公因数的值
	tryCd = 2
	while tryCd <= a and tryCd <= b:
		if a % tryCd == 0 and b % tryCd == 0:
			cd *= tryCd
			a //= tryCd
			b //= tryCd
		else:
			tryCd += 1
	return cd

辗转相除法

辗转相除法又称欧几里德算法,因为首次出现在古希腊数学家欧几里德的《几何原本》中,因此得名。

算法定义

两个正整数 a, b ,两数相除得到一个余数 r,然后再用除数 b 除以余数 r,依次规律反复进行。直到余数为0,则此时的除数就是 a,b 的最大公约数。

算法示例

计算130,156的最大公约数

设 a = 130,b = 156
对两数进行辗转相除
156 mod 130 = 26
130 mod 26 = 0
所以 30,156的最大公约数等于 26

算法图示

image.png

编程实现

def gcd(a, b):
	# 求余数
	remainder = a % b
	# 如果余数为0,结束递归,此时除数就是最大公约数
	if remainder == 0:
		return b
	return gcd(b, remainder)

需要注意,程序在第一次计算时并未规定两数的顺序,因为一个较小的数除以一个较大的数的余数等于较小的数,所以即使较小数在前,在第二循环时就会变成,较大数在前,较小数在后。

算法证明

我们很容易的看到,辗转相除法成立的前提依赖以下定理

两个正整数的最大公约数等于较小的数和 较大的数与较小的数的余数 的最大公约数

即:a,b 为正整数,a > b, 则 gcd(a,b) = gcd(a , a mod b)

证明过程如下:

设 a 除以 b 商 k 余 r, 则 r = a - kb, a = kb + r

假设 a, b 存在公约数d,则 d|a, d|b 成立

公式 r = a - kb 两边同时除以 d, 由上已知条件知道 (a - kb)/d 必定为整数,则 r 除以 d 的结果为整数,则 d|r 成立

即a,b的公约数一定为 b, a mod b 的公约数成立

假设 b, a mod b存在公约数d,则 d|b, d|(a mod b) 成立,即 d|r成立

公式 a = kb + r 两边同时除以 d, 由上已知条件知道 (kb + r)/d 必定为整数,则 a 除以 d 的结果为整数,则 d|a 成立

即 b, a mod b 的公约数一定为 a,b 的公约数成立

由a,b的公约数一定为 b, a mod b 的公约数,b, a mod b 的公约数一定为 a,b 的公约数得出 a,b 的公约数和 b, a mod b 的公约数完全一样

则 a,b 的最大公约数等于 b, a mod b 的最大公约数成立

更相减损术

更相减损术出自我国古代著名的数学典籍《九章算术》,是一种可以很方便的计算两数最大公约数的算法。

算法定义

步骤1:两个正整数 a, b , 如果两者同时能被2整除,则同时除以2,依次反复,直到不能同时被2整除为止

步骤2:两数较大的数减去较小的数得到差,然后再拿较小的数和差两数中的较大数减去较小数,依次反复,直到较小的数和差相等

则 a,b 的最大公约数就是最初除以所有2的乘积再乘以最后步骤2得到的值

算法示例

计算130,156的最大公约数

设 a = 130,b = 156
因为 a,b都可以被2整除
则 a = 130/2 = 65, b = 156 /2 = 78

此时a,b不能再被2整除,对两数进行辗转相减
78-65=13
65-13=52
52-13=39
39-13=26
26-13=13
因为 13等于13
所以 30,156的最大公约数等于 2 * 13 = 26

编程实现

def gcd(a, b):
	cd = 1;
	while a % 2 == 0 and b % 2 == 0:
		a //= 2
		b //= 2
		cd *= 2
		
	while a != b:
		if(a > b):
			a = a - b
		else:
			b = b - a
	return cd * a

算法证明

首先我们要明白一点,对于:如果两者同时能被2整除,则同时除以2这条定义不是必须的,这样做的目的只是让数字变小一些,可以更快的计算。或者简单理解,就是提前把公因数2提取出来而已。

算法的最后一步如果较小的数和差相等,直接取当前这两个数的值是因为,如果两个数相等,则两数的最大公约数等于自身,即取的是两数的最大公约数。

更相减损术从原理上来说和辗转相除法是相通的,对同一个数多次相减本质上和用除法求余是完全一样的,只是更相减损术用减法,辗转相除法用的是除法。

我们很容易的看到,更相减损术成立的前提依赖以下定理

两个正整数的最大公约数等于较小的数 和 较大的数与较小的数的差 的最大公约数

即:a,b 为正整数,a > b, 则 gcd(a,b) = gcd(a , a - b)

证明过程如下:

设 a 减去 b 等于k, k = a - b, a = b + k

假设 a, b 存在公约数d,则 d|a, d|b 成立

公式 k = a - b 两边同时除以 d, 由上已知条件知道 (a - b)/d 必定为整数,则 k 除以 d 的结果为整数,则 d|k 成立

即a,b的公约数一定为 b, a - b 的公约数成立

假设 b, a - b存在公约数d,则 d|b, d|(a - b) 成立,即 d|k成立

公式 a = b + k 两边同时除以 d, 由上已知条件知道 (b + k) /d 必定为整数,则 a 除以 d 的结果为整数,则 d|a 成立

即 b, a - b 的公约数一定为 a,b 的公约数成立

由a,b的公约数一定为 b, a - b 的公约数,b, a - b 的公约数一定为 a,b 的公约数得出 a,b 的公约数和 b, a - b 的公约数完全一样

则 a,b 的最大公约数等于 b, a - b 的最大公约数成立

总结

短除法和质因数分解法其实都是运用了枚举法的思想,因此在质公因数比较小的的情况下,使用很方便。但在质公因数比较大时,质公因数不太容易看出来,即使用计算机来完成这个枚举的过程,时间复杂度还是比较高。

此时用辗转相除法或更相减损术则更胜一筹。两者的思想相同,前者用除法,时间复杂度为 O(logN),后者用减法,时间复杂度为 O(N)。因此更推荐使用辗转相除法。

感想

学习数学在于知其然并知其所以然,不然只知其表,不知其原理,注定如镜花水月,徒劳一场,与君共勉。