[Swift]求最大公约数和最小公倍数

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/11032151.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

1、定义:

GCD:最大公约数(Greatest Common Divisor)。指两个或多个整数共有约数中最大的一个。

LCM:最小公倍数(Least Common Multiple)。两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数。

2、最小公倍数与最大公约数的关系: 

LCM(A,B)×GCD(A,B)=A×B

其中LCM是最小公倍数,GCD是最大公约数。

1  //MARK:求最小公倍数
2  func lcm(_ a:Int,_ b:Int) -> Int
3  {
4     //求最大公约数
5     let num:Int = gcd(a,b)
6     return a * b / num
7  }

所以求最小公倍数的问题可以转化为求最大公约数。

3、公约数的性质

如果b是A和B的公约数,则有:

(1)、b也是A+B的约数,即b是A,B,A+B的公约数

(2)、b也是A-B的约数,即b是A,B,A-B的公约数

(3)、更一般地,对于任意整数x、y,b也是Ax+By的约数,即b是A,B,Ax+By的公约数

(4)、根据性质(3),r = A - kB = A mod B,所以A mod B也是A+B的约数,mod是求余运算,即b是A,B,A mod B的公约数

1 gcd(A,B) 
2 = gcd(B,A) 
3 = gcd(A,A+B)
4 = gcd(A,A-B) 
5 = gcd(A,Ax+By) 
6 = gcd(A,A mod B)

求最大公约数的几个算法:

(1)、质因数分解法

(2)、更相减损术:

缺点:当两个数a和b相差太过悬殊时,递归的次数会非常多,严重影响算法性能。

(3)、辗转相除法(欧几里得算法)

缺点:因为使用了求余运算的缘故导致其在面对大整数的时候性能不够理想。我们应尽量避免使用求余运算。

(4)、Stein算法:更相减损术 + 辗转相除法

4、质因数分解法:把每个数分别分解质因数,再把各数中的全部公有质因数提取出来连乘,所得的积就是这几个数的最大公约数。

 1 //MARK:质因数分解法。不能处理为0的情况
 2 func gcd(_ a:Int,_ b:Int) -> Int
 3 {
 4     var a = a
 5     var b = b
 6     guard a != 0 && b != 0 else
 7     {
 8         return 0
 9     }
10     
11     let min:Int = a < b ? a : b
12     var accumulate:Int = 1
13     
14     // 以2进行分解,如果为0则变成死循环
15     while ((a & 1) == 0 && (b & 1) == 0)
16     {
17         accumulate *= 2
18         a >>= 1
19         b >>= 1
20     }
21     
22     // 以大于等于3的数进行分解
23     for i in stride(from:3,through:min,by:2)
24     {
25         while ((a % i) == 0 && (b % i) == 0)
26         {
27             accumulate *= i
28             a /= i
29             b /= i
30         }
31     }
32     // 将所有公因子的乘积作为返回值
33     return accumulate
34 }
测试:
1 print(gcd(100,1000))
2 //Print 100

4、更相减损术:出自《九章算术》,成于公元一世纪左右。其作者已不可考。一般认为它是经历代各家的增补修订,而逐渐成为现今定本的,最后成书最迟在东汉前期,现今流传的大多是在三国时期魏元帝景元四年(263年),刘徽为《九章》所作的注本。

原文:

可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。

1 如果A > B,则 gcd(A,B) = gcd(B,A-B)
2 如果A < B,则 gcd(A,B) = gcd(A,B-A)

译文:

第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。

第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。

则第一步中约掉的若干个2的积与第二步中等数的乘积就是所求的最大公约数。其中所说的“等数”,就是公约数。

如图所示:两条线段长分别可表示252和105,则其中每一小分段长代表最大公约数21。如动画所示,只要辗转地从大数中减去小数,直到其中一段的长度为0,此时剩下的一条线段的长度就是252和105的最大公因数。

更相减损术的一般写法:

 1 //MARK:更相减损术
 2 func gcd(_ a:Int,_ b:Int) -> Int
 3 {
 4     var a = a
 5     var b = b
 6     //储存第一步中约掉的若干个2
 7     var ans:Int = 1
 8     //如果ab均为偶数则用2约简
 9     while(a%2==0  &&  b%2==0)
10     {
11         a /= 2
12         b /= 2
13         ans *= 2
14     }
15     //判断两数是否相等,也可以理解为直到所得的减数和差相等为止
16     while(a != b)
17     {
18         if a > b
19         {
20             //以较大的数减较小的数
21             a -= b
22         }
23         else
24         {
25             //以较大的数减较小的数
26             b -= a
27         }
28     }
29     //求第一步中约掉的若干个2与第二步中等数的乘积,并返回
30     return a*ans
31 }
更相减损术的递归写法:
1 import Foundation
2 //MARK:更相减损术
3 func gcd(_ a:Int,_ b:Int) -> Int
4 {
5     if a == b {return a}
6     return gcd(abs(a - b), min(a, b))
7 }

更相减损术的while循环写法:

 1 //MARK:更相减损术
 2 func gcd(_ a:Int,_ b:Int) -> Int
 3 {
 4     var a = a
 5     var b = b
 6     while (true)
 7     {
 8         if a > b
 9         {
10             a -= b
11         }
12         else if a < b
13         {
14             b -= a
15         }
16         else
17         {
18             return a
19         }
20     }
21 }

测试:

1 print(gcd(100,1000))
2 //Print 100

5、辗转相除法:又称欧几里得算法。两个正整数A,B的最大公约数等于其中较小值与两数相除的余数的最大公约数。

1 gcd(A, B) = gcd(B, A mod B) 
2 其中:A > B

例如,计算a = 1071和b = 462的最大公约数的过程如下:从1071中不断减去462直到小于462(可以减2次,即商q0 = 2),余数是147:

  • 1071 = 2 × 462 + 147.

然后从462中不断减去147直到小于147(可以减3次,即q1 = 3),余数是21:

  • 462 = 3 × 147 + 21.

再从147中不断减去21直到小于21(可以减7次,即q2 = 7),没有余数:

  • 147 = 7 × 21 + 0.

此时,余数是0,所以1071和462的最大公约数是21,这和用素因数分解得出的结果相同。

算法的演示动画:最初的绿色矩形的长和宽分别是a = 1071、b = 462,从中不断铺上462×462的正方形直到剩下部分面积是462×147;然后再铺上147×147的正方形直至剩下21×147的面积;最后,铺上21×21的正方形时,绿色部分就没有了。即21是1071和462的最大公约数。 

辗转相除法的递归写法:

1 //MARK:辗转相除法
2 func gcd(_ a:Int,_ b:Int) -> Int
3 {
4     return b == 0 ? a : gcd(b, a % b)
5 }

辗转相除法while循环写法

 1 //MARK:辗转相除法
 2 func gcd(_ a:Int,_ b:Int) -> Int
 3 {
 4     var a = a
 5     var b = b
 6     while(b != 0)
 7     {
 8         let r:Int = a % b
 9         a = b
10         b = r
11     }
12     return a
13 }

测试:

1 print(gcd(100,1000))
2 //Print 100

6、Stein算法:Stein算法由J.Stein 1961年提出,Stein算法只进行整数的移位和加减法,没有求余运算。

算法的原理:

(1)、若a和b都是偶数,则记录下公约数2,然后都除2(即右移1位);

(2)、若其中一个数是偶数,则偶数除2,因为此时2不可能是这两个数的公约数。

(3)、若两个都是奇数,则a = |a-b|,b = min(a,b),因为若d是a和b的公约数,那么d也是|a-b|和min(a,b)的公约数。

注:判断偶数的两种方式:

(1)、num % 2 == 0

(2)、num & 1 == 0

Stein算法的递归写法:

 1 //MARK:Stein算法
 2 func gcd(_ a:Int,_ b:Int) -> Int
 3 {
 4     if a == 0 {return b}
 5     if b == 0 {return a}
 6     if (a & 1) == 0 && (b & 1) == 0
 7     {
 8         return 2 * gcd(a >> 1, b >> 1)
 9     }
10     else if (a & 1) == 0
11     {
12         return gcd(a >> 1, b)
13     }
14     else if (b & 1) == 0
15     {
16         return gcd(a, b >> 1)
17     }
18     else
19     {
20         return gcd(abs(a - b), min(a, b))
21     }
22 }

Stein算法的while循环写法:

 1 //MARK:Stein算法
 2 func gcd(_ a:Int,_ b:Int) -> Int
 3 {
 4     var a = a
 5     var b = b
 6     var acc:Int = 0
 7     while((a & 1) == 0 && (b & 1) == 0)
 8     {
 9         acc += 1
10         a >>= 1
11         b >>= 1
12     }
13     while ((a & 1) == 0) {a >>= 1}
14     while ((b & 1) == 0) {b >>= 1}
15     if a < b {swap(&a,&b)}
16     while (a != 0)
17     {
18         while ((a & 1) == 0)
19         {
20             a >>= 1
21         }
22         if a < b{swap(&a,&b)}
23         a = (a - b) >> 1
24     }
25     return b << acc
26 }
27 28 func swap(_ x:inout Int,_ y:inout Int)
29 {
30     x = x ^ y
31     y = x ^ y
32     x = x ^ y
33 }

测试:

1 print(gcd(100,1000))
2 //Print 100
 
原文地址:https://www.cnblogs.com/strengthen/p/11032151.html