前兩天在看書時,遇到一個求最大公約數的程式題,我思索良久,居然想不起來該如何解答。
那一瞬間我彷彿看到了我的小學數學老師手拿三角板正在怒氣衝衝的看著我,責問我為什麼這麼簡單的數學題都不會。於是我就厚著臉皮再次重溫了一下小學的數學知識,以下是記錄的筆記。
短除法
短除法是求最大公約數最為簡單的方法之一,還依稀記得我小學的數學老師教的就是這一種。
演算法定義
不斷除以兩數的公因數,直到兩數不再有公因數,把前面所有的除數相乘,即為兩數的最大公約數。
演算法示例
計算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
演算法圖示
程式設計實現
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),因此更推薦使用輾轉相除法。
感想
學習數學在於知其然並知其所以然,不然只知其表,不知其原理,註定如鏡花水月,徒勞一場,與君共勉。