求解最大公约数的四种算法
这是一次计算机导论课的作业。本来对于最大公约数的求解算法,我就只知道一个辗转相除法。原来,其实还有别的一些 ...
法一:试除法(穷举法)
也许这应该才是最先能想到的算法 —— 两个数中取小的那个,由大到小穷举这个数的所有因数,并且看看这个数是不是另一个数的因数,如果是,那这个数就是这两个数的最大公约数了。
1. 时间复杂度
$O(min(a,b))$
1. 自然语言描述
- 定义变量 $a,b$,用于存放两个待求取最大公约数的值,确保 $a\leq b$;
- 定义变量 $i=a$;
- 如果 $i\geq1$,执行步骤 4;
- 判断 $i$ 是否是 $a$ 的因数,如果是,执行步骤 5,否则,执行步骤 7;
- 判断 $i$ 是否是 $b$ 的因数,如果是,执行步骤 6,否则,执行步骤 7;
- 跳出循环,$i$ 就是 $a$ 和 $b$ 的最大公约数;
- $i$ 自减 $1$,执行步骤 3;
1. 伪代码描述
1 | var a,b,i:integer; |
1. 流程图
1. C++ 代码
1 |
|
法二:辗转相除法(欧几里得算法)
这大概是最常见的计算最大公约数的算法了吧...
2. 时间复杂度
可近似看作 $O (log (max (a,b)))$,但取模运算性能较差。
2. 自然语言描述
- 定义变量 a,b 并读入;
- 如果 b == 0,返回 a;
- 否则,更新 a 的值为原来 b 的值,更新 b 的值为原来 a% b 的值,回到步骤 2。
2. 伪代码描述
1 | var a,b:integer; |
2. 流程图
2. C++ 代码
主函数及 gcd 函数的函数声明:
1 |
|
gcd 函数(递归实现):
1 | uint64_t gcd(uint64_t a, uint64_t b) |
gcd 函数(递归函数,写成一行的版本):
1 | uint64_t gcd(uint64_t a, uint64_t b) { return b ? gcd(b, a % b) : a; } |
gcd 函数(迭代实现):
1 | uint64_t gcd(uint64_t a, uint64_t b) |
法三:更相减损法
更相减损法又叫更相减损术,出自《九章算术》,是咱老祖宗的智慧。这个东西本来是为了约分而设计的,但是,既然都约分了,那自然也可以用来求取最大公约数。
这个算法的原文描述是这样:
可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。
翻译成白话就是:
(如果需要对分数进行约分,那么)可以折半的话,就折半(也就是用 2 来约分)。如果不可以折半的话,那么就比较分母和分子的大小,用大数减去小数,互相减来减去,一直到减数与差相等为止,用这个相等的数字来约分。
这两段引用的来源是:更相减损术_百度百科 (baidu.com)
3. 时间复杂度
$O(max(a,b))$
3. 自然语言描述
- 定义变量 a、b 并读入;
- 如果 变量 a、b 能被 2 整除,那就都除以 2。不断重复执行这一步直到 a、b 任意一个不能被 2 整除,记录下进行这一步的次数,存入变量 cnt 中;
- 定义三个变量 x1、x2、x3,用于表示被减数、减数和差;
- x1 赋初值为 a、b 中较大的那一个,x2 赋初值为 a、b 中较小的那一个,x3 赋初值为 x1-x2;
- 在 x2!=x3 的情况下,不断更新 x1 = max (x2, x3),x2 = min (x2, x3),x3 = x1 - x2;
- 返回 x2 + pow (2, cnt)。
3. 流程图
3. C++ 代码
下边给出的是基于原文描述实现的更相减损法:
1 | uint64_t gcd(uint64_t a, uint64_t b) |
如果去掉那些 “可半者半之”,直接进行后面的 “辗转相减” 部分,也是可以的:
1 | uint64_t gcd(uint64_t a, uint64_t b) |
于是也可以写出递归形式:
1 | uint64_t gcd(uint64_t a, uint64_t b) |
法四:Stein 算法
这个算法是辗转相除法的改进版本,避免了取模运算,且算法性能稳定。
4. 时间复杂度
$O(log(max(a,b)))$
版本一:
学习自这篇文章,正好学习一下位运算的一些 “骚操作”(见下文引用处)。
4.1. 自然语言描述
- 定义变量 a、b 并读入,确保 a>=b(如果 a<b 则交换);
- 如果两个数都是偶数,那就不断除以 2 直至至少一个不是偶数;
- 如果一奇一偶,那就把那个偶数不断除以 2 直至它也为一个奇数;
- 对两个奇数进行辗转相减(或者辗转相除?上边那篇文章里这么说,但是除的话不是无法避免取模运算效率低下的问题了嘛 emm),直至求出它们的最大公约数;
4.1. 流程图
4.1. C++ 代码
(1) 按位与 (&):
a&x 为对数 a 的二进制形式的取位操作,即去 a 二进制形式的第 x 位。这里有一个重要应用就是 a&1 可以用于判断数 a 的奇偶性,即 a 末位为 0 即为偶数,末位为 1 即为奇数。
(2) 异或运算 (^):
具体介绍参考之前的随笔:http://www.cnblogs.com/COLIN-LIGHTNING/p/8298554.html;
应用为交换两数:a^=b,b^=a,a^=b 即完成了两数交换。(3) 按位左移 (<<):
a<<=x 即为使 a 乘以 2 的 x 次幂,原理是让 a 的二进制形式左移 x 位;应用为对与 2 的幂次方相乘使运算更快更方便;
(4) 按位右移 (>>):
a>>=x 即为使 a 除以 2 的 x 次幂,原理是让 a 的二进制形式右移 x 位;应用为对与 2 的幂次方相除使运算更快更方便;
大佬的代码大致是这样的:
1 | uint64_t gcd(uint64_t a, uint64_t b) |
版本二:
来自这里,对均为奇数的情况做了不同的处理,其他都是一样的。
4.2. 自然语言描述
- 定义变量 a、b 并读入;
- 如果 a==b,则直接返回 a 或 b,否则下一步;
- 如果 a<b,交换 a、b 的值,确保 a>b;
- 判断属于下边哪种情况,按对应的情况更新 a、b 的值,回到步骤 2。
四种情况分别是:
- 均为偶数: gcd (a,b) = 2 * gcd (a/2,b/2);
- 均为奇数: gcd (a,b) = gcd ((a+b)/2,(a-b)/2);
- a 为奇数,b 为偶数: gcd (a,b) = gcd (a,b/2);
- a 为偶数,b 为奇数: gcd (a,b) = gcd (a/2,b);
4.2. 流程图
4.2. C++ 代码
最后代码和上边也没有太大差别。
1 | uint64_t gcd(uint64_t a, uint64_t b) |
参考资料:
- 《求最大公约数的 4 种常用算法 AmethystFOB 的博客 - CSDN 博客求最大公约数的四种算法》
- 《更相减损术_百度百科 (baidu.com)》
- 《教你写一手漂亮的伪代码(详细规则 & 简单实例)_陈同学的博客 - CSDN 博客_伪代码的简单例子》
- 《伪代码是什么?如何写一个伪代码?-C#.Net 教程 - PHP 中文网》
- 《流程图_百度百科 (baidu.com)》
- 《for、while、do while 三种循环的流程图画法总结(附案例) - 知乎 (zhihu.com)》
- 《switch 语句流程图怎么画?简单的 switch 语句流程图模板分享 (liuchengtu.com)》
- 《浅谈 Stein 算法求最大公约数 (GCD) 的原理及简单应用 - COLINGAO - 博客园 (cnblogs.com)》