最大公約數是一個很經典的數學問題,對於這個問題有四種通用的解法,質因數分解法,短除法,不過比較常用的還是輾轉相除法,算法出自於歐幾里的著作《幾何原本》,還有一個就是出自《九章算術》的更相減損法,一般實現的時候都是通過輾轉相除法實現,基本的邏輯是這樣的:假設把a和b的最大公約數表示成為f(a,b),並且a>=b>0。現在取k=a/b,m=a%b,則a=k*b+m,變形為m=a - k*b;x和y能被f(a,b)整除,那么m也能被f(a,b)整除,f(a,b) = f(b,a%b)。
循環取值
基於上面的邏輯我們定義兩個數字a,b,首先mod=a%b余數,然后將b賦值給a,mod賦值給b,跳出循環返回a就是最大公約數,代碼如下:
-(NSInteger)maxDivisor:(NSInteger)a secondNumber:(NSInteger)b{ if (a<b) { a=a+b; b=a-b; a=a-b; } while (b!=0) { NSInteger mod=a%b; a=b; b=mod; } return a; }
輾轉相除遞歸版
出於對循環的理解,我們可以通過遞歸實現:
-(NSInteger)maxmodRecursive:(NSInteger)a secondNumber:(NSInteger)b{ if (a<b) { a=a+b; b=a-b; a=a-b; } if (b==0) { return a; }else{ return [self maxmodRecursive:b secondNumber:a%b]; } }
輾轉相除的變形
輾轉相除法對大整數求最大公約數,輾轉相除法的效率就出現了瓶頸,對於大整數而言,取模運算(用到除法)的開銷非常的昂貴,這是歐幾里得算法的局限性,其實我們可以借鑒歐幾里得的輾轉相除法優化一下。我們需要認識到一個數學知識就是兩個數的最大公約數等於較小數和兩個數差值之間的公約數。可以通過“-”運算,即 f(a,b)=f(a-b,b)。
-(NSInteger)maxRecursive:(NSInteger)a secondNumber:(NSInteger)b{ if (a<b) { a=a+b; b=a-b; a=a-b; } if (b==0) { return a; }else{ return [self maxRecursive:b secondNumber:a-b]; } }
對於大數運算問題通過以上形式解決了,那就是當a和b相差很多時,算法的迭代次數會過高,導致了算法的效率較低,a=1000,b=1,們要考慮其他的優化。
移位運算
上面的方法對於大整數和迭代的問題沒能很好的解決,我們可以通過移位運算很好解決以上的問題:
(1)如果b=k*b1,a=k*a1.那么有f(a,b)=k*f(a1,b1)
(2)如果a=p*a2,p為素數,並且b%p != 0,那么f(a,b) = f(p*a2,b) = f(a2,b)
於是我們得到下面的解決方法:
將p = 2,
若a,b均為偶數,f(a,b) = 2*f(a/2,b/2) = 2*f(a>>1,b>>1);
若a是偶數,b是奇數,f(a,b) = f(a/2,b) = f(a>>1,b);
若a是奇數,b是偶數,f(a,b) = f(a,b/2) = f(a,b>>1);
若a和b均為奇數,f(a,b) = f(b,a-b)。這時a-b一定是偶數,下一步一定會除以2,算法的時間復雜度是O(log2(max(a,b))。
-(NSInteger)maxLogic:(NSInteger)a secondNumber:(NSInteger)b{ if (a<b) { a=a+b; b=a-b; a=a-b; } if (b==0) { return a; }else{ //a是偶數 if ((a&1)==0) { //b是偶數 if ((b&1)==0) { return [self maxLogic:a>>1 secondNumber:b>>1]<<1; }else{//b是奇數 return [self maxLogic:a>>1 secondNumber:b]; } }else{//a是奇數 //b是偶數 if ((b&1)==0) { return [self maxLogic:a secondNumber:b>>1]; }else{//b是奇數 return [self maxLogic:b secondNumber:a-b]; } } } return 0; }
通過移位運算和減法運算,避開了大整數除法,提高了算法的效率,有些東西還是需要經常研究的~