漫畫算法:輾轉(zhuǎn)相除法是什么鬼? - 文章 - 伯樂在線
大四畢業(yè)前夕,計(jì)算機(jī)學(xué)院的小灰又一次頂著炎炎烈日,
去某IT公司面試研發(fā)工程師崗位……
半小時(shí)后,公司會(huì)議室,面試開始……
漫畫算法:輾轉(zhuǎn)相除法是什么鬼? - 文章 - 伯樂在線
小灰奮筆疾書,五分鐘后……
小灰的思路十分簡(jiǎn)單。他使用暴力枚舉的方法,試圖尋找到一個(gè)合適的整數(shù) i,看看這個(gè)整數(shù)能否被兩個(gè)整型參數(shù)numberA和numberB同時(shí)整除。
這個(gè)整數(shù) i 從2開始循環(huán)累加,一直累加到 numberA 和 numberB 中較小參數(shù)的一半為止。循環(huán)結(jié)束后,上一次尋找到的能夠被兩數(shù)整除的最大 i 值,就是兩數(shù)的最大公約數(shù)。
事后,垂頭喪氣的小灰去請(qǐng)教同系的學(xué)霸大黃……
輾轉(zhuǎn)相除法, 又名歐幾里得算法(Euclidean algorithm),目的是求出兩個(gè)正整數(shù)的最大公約數(shù)。它是已知最古老的算法, 其可追溯至公元前300年前。
這條算法基于一個(gè)定理:兩個(gè)正整數(shù)a和b(a>b),它們的最大公約數(shù)等于a除以b的余數(shù)c和b之間的最大公約數(shù)。比如10和25,25除以10商2余5,那么10和25的最大公約數(shù),等同于10和5的最大公約數(shù)。
有了這條定理,求出最大公約數(shù)就簡(jiǎn)單了。我們可以使用遞歸的方法來(lái)把問(wèn)題逐步簡(jiǎn)化。
首先,我們先計(jì)算出a除以b的余數(shù)c,把問(wèn)題轉(zhuǎn)化成求出b和c的最大公約數(shù);然后計(jì)算出b除以c的余數(shù)d,把問(wèn)題轉(zhuǎn)化成求出c和d的最大公約數(shù);再然后計(jì)算出c除以d的余數(shù)e,把問(wèn)題轉(zhuǎn)化成求出d和e的最大公約數(shù)……
以此類推,逐漸把兩個(gè)較大整數(shù)之間的運(yùn)算簡(jiǎn)化成兩個(gè)較小整數(shù)之間的運(yùn)算,直到兩個(gè)數(shù)可以整除,或者其中一個(gè)數(shù)減小到1為止。
五分鐘后,小灰改好了代碼……
更相減損術(shù), 出自于中國(guó)古代的《九章算術(shù)》,也是一種求最大公約數(shù)的算法。
他的原理更加簡(jiǎn)單:兩個(gè)正整數(shù)a和b(a>b),它們的最大公約數(shù)等于a-b的差值c和較小數(shù)b的最大公約數(shù)。比如10和25,25減去10的差是15,那么10和25的最大公約數(shù),等同于10和15的最大公約數(shù)。
由此,我們同樣可以通過(guò)遞歸來(lái)簡(jiǎn)化問(wèn)題。首先,我們先計(jì)算出a和b的差值c(假設(shè)a>b),把問(wèn)題轉(zhuǎn)化成求出b和c的最大公約數(shù);然后計(jì)算出c和b的差值d(假設(shè)c>b),把問(wèn)題轉(zhuǎn)化成求出b和d的最大公約數(shù);再然后計(jì)算出b和d的差值e(假設(shè)b>d),把問(wèn)題轉(zhuǎn)化成求出d和e的最大公約數(shù)……
以此類推,逐漸把兩個(gè)較大整數(shù)之間的運(yùn)算簡(jiǎn)化成兩個(gè)較小整數(shù)之間的運(yùn)算,直到兩個(gè)數(shù)可以相等為止,最大公約數(shù)就是最終相等的兩個(gè)數(shù)。
五分鐘后,小灰重寫了代碼……
眾所周知,移位運(yùn)算的性能非常快。對(duì)于給定的正整數(shù)a和b,不難得到如下的結(jié)論。其中g(shù)cb(a,b)的意思是a,b的最大公約數(shù)函數(shù):
當(dāng)a和b均為偶數(shù),gcb(a,b) = 2*gcb(a/2, b/2) = 2*gcb(a>>1, b>>1)
當(dāng)a為偶數(shù),b為奇數(shù),gcb(a,b) = gcb(a/2, b) = gcb(a>>1, b)
當(dāng)a為奇數(shù),b為偶數(shù),gcb(a,b) = gcb(a, b/2) = gcb(a, b>>1)
當(dāng)a和b均為奇數(shù),利用更相減損術(shù)運(yùn)算一次,gcb(a,b) = gcb(b, a-b), 此時(shí)a-b必然是偶數(shù),又可以繼續(xù)進(jìn)行移位運(yùn)算。
比如計(jì)算10和25的最大公約數(shù)的步驟如下:
整數(shù)10通過(guò)移位,可以轉(zhuǎn)換成求5和25的最大公約數(shù)
利用更相減損法,計(jì)算出25-5=20,轉(zhuǎn)換成求5和20的最大公約數(shù)
整數(shù)20通過(guò)移位,可以轉(zhuǎn)換成求5和10的最大公約數(shù)
整數(shù)10通過(guò)移位,可以轉(zhuǎn)換成求5和5的最大公約數(shù)
利用更相減損法,因?yàn)閮蓴?shù)相等,所以最大公約數(shù)是5
在兩數(shù)比較小的時(shí)候,暫時(shí)看不出計(jì)算次數(shù)的優(yōu)勢(shì),當(dāng)兩數(shù)越大,計(jì)算次數(shù)的節(jié)省就越明顯。
最后總結(jié)一下上述所有解法的時(shí)間復(fù)雜度:
1.暴力枚舉法:時(shí)間復(fù)雜度是O(min(a, b)))
2.輾轉(zhuǎn)相除法:時(shí)間復(fù)雜度不太好計(jì)算,可以近似為O(log(min(a, b))),但是取模運(yùn)算性能較差。
3.更相減損術(shù):避免了取模運(yùn)算,但是算法性能不穩(wěn)定,最壞時(shí)間復(fù)雜度為O(max(a, b)))
4.更相減損術(shù)與移位結(jié)合:不但避免了取模運(yùn)算,而且算法性能穩(wěn)定,時(shí)間復(fù)雜度為O(log(max(a, b)))
本文原本只寫到輾轉(zhuǎn)相除法就終告結(jié)束,后來(lái)網(wǎng)友們指出還有更優(yōu)化的解法,看來(lái)自己還是才疏學(xué)淺,很感謝大家指出問(wèn)題。另外,方法的參數(shù)默認(rèn)必定是正整數(shù),所以在代碼中省去了合法性檢查。
文中描述的更相減損術(shù)是簡(jiǎn)化了的方式。在九章算術(shù)原文中多了一步驗(yàn)證:如果兩數(shù)都是偶數(shù),計(jì)算差值之前會(huì)首先讓兩個(gè)數(shù)都折半,使得計(jì)算次數(shù)更少。這種方法做到了部分優(yōu)化,但古人似乎沒想到一奇一偶的情況也是可以優(yōu)化的。