最大公约数是一个很经典的数学问题,对于这个问题有四种通用的解法,质因数分解法,短除法,不过比较常用的还是辗转相除法,算法出自于欧几里的著作《几何原本》,还有一个就是出自《九章算术》的更相减损法,一般实现的时候都是通过辗转相除法实现,基本的逻辑是这样的:假设把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; }
通过移位运算和减法运算,避开了大整数除法,提高了算法的效率,有些东西还是需要经常研究的~