概述

最大公约数(Greatest Common Divisor),也称最大公因数、最大公因子。是指两个或多个整数公有因数中最大的一种。编程时常用 gcd(a, b) 表示两个数的最大公约数。

相反的,将两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数(Least Common Multiple)。编程时候用 lcm(a, b) 表示两个数的最大公约数。

其中 lcm(a, b) = a * b / gcd(a, b);

所以在计算 gcd 和 lcm 时,通常只需要计算出 gcd 即可。本文中也主要阐述最大公约数的算法。

算法实现

在算法学习的过程中,对于最大公约数求解的算法有很多,常见的有穷举法,分解质因数法,短除法,更相减损法,欧几里得算法(辗转相除法)等。这里笔者对于最大公约数求解的常见算法及实现进行简单是阐述。

穷举法

穷举法实现最大公约数的思路较为简单。其算法步骤如下

gcd(m ,n)

1.找出参数m,n中较小的一个。并以此作为循环的起点i;

2.检查 i 是否。若 i 为m,n的公因数,那么 i 即为m,n的最大公因数;

3.若 i 不为m,n的公因数,那么就对 i 自减,直至满足步骤2的条件或者 i == 1。

该算法建立在两个数m,n的最大公因数一定不大于min(m, n)这一合理推论之上。只要从min(m, n)向下自减,遇到的第一个公因数就一定是最大公因数。该算法实现如下

//穷举法
public static long gcd(long m, long n) {for (long i = Math.min(m, n); i > 1; i--) {if (m % i == 0 && n % i == 0) {return i;}}return 1;
}

分解质因数法

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

该算法实现要点在于如何找出两个数的公有质因数。笔者的实现中采用类似线性素数筛的方式,保证能求得最终所有公共质因数的积。下面给出算法步骤

1.设置保存结果的变量result,且初始化为1。

2.置循环起点 i 为2,最大不超过min(m, n)。当 m 或 n 等于1时,退出循环。

3.若 i 为m,n的公约数,那么 m /= i,n /= i,result *= i。否则 i++。直至不满足循环条件后退出。

该算法的实现代码如下

// 分解质因数法
public static long gcd(long m, long n) {long result = 1;for (int i = 2; i <= Math.min(m, n) && m != 1 && n != 1;) {if (m % i == 0 && n % i == 0) {m /= i;n /= i;result *= i;} else {i++;}}return result;
}

需要注意的是,在分解质因数时,常用的一种方法是短除法。它同样可以用来求解最大公约数,但其实质上只是分解质因数法的一种运用形式,所以本文中不将其作为一种算法单独列出。

更相减损法

更相减损法是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。又名“更相减损术”,辗转相减法,等值算法,尼考曼彻斯法。

《九章算术》中对这种方法的说明如下:

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

将其转化为算法步骤如下:

1.对于两个参数m,n。若两者都是偶数,那么用2进行约分。否则继续执行步骤2;

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

3.最后将步骤一中约掉的若干个2与步骤二中的"等数"相乘就是所求的最大公约数。

其实现代码如下

// 更相减损法——递归实现
public static long gcd(long m, long n) {if (m == n)return m;if ((m & 1) == 0 && (n & 1) == 0)return gcd(m >> 1, n >> 1) * 2;return gcd(Math.abs(m - n), Math.min(m, n));
}

递归实现相对来说代码更为简洁,但因为存在方法调用,所以开销相对更大。因此这里给出更相减损法的循环实现。

// 更相减损法——循环实现
public static long gcd(long m, long n) {long mul = 1;while ((m & 1) == 0 && (n & 1) == 0) {m >>= 1;n >>= 1;mul <<= 1;}while (m != n) {if (m > n) {m = m - n;} else {n = n - m;}}return mul * n;
}

欧几里得算法

欧几里德算法又称辗转相除法,是指用于计算两个正整数a,b的最大公约数。应用领域有数学和计算机两个方面。计算公式gcd(a,b) = gcd(b,a mod b)。

欧几里得算法的公式十分简单,实现算法的代码量也极少,是求解最大公约数的一种较好的算法。对于该算法,仅仅会使用是不够的,所以在这里笔者对于欧几里得算法进行一下简单的证明。

对于一个正整数m,它可以表示成m = kn + r(其中m,k,n,r均为正整数,且m > n,n > r)

故 r = m mod n

假设 g 为 m,n 的一个公约数,记作 g|m,g|n。即 m mod g == 0,n mod g == 0。

同时 r = m - kn,两边同除以g。得

r/g = m/g - kn/g,不难发现 m/g - kn/g 结果为整数(因为g|m,g|n,且k为整数)

故 g 是 m,n,r(也即m mod n)的公约数

本着归约的思想,得出以下公式

gcd(m, n) = gcd(n, m mod n)

假设 g 是 n,m mod n的公约数,则 g|n,g|(m - kn)(k∈N*)

进而 g|m,因此g也是m,n的公约数

故(m, n)和(n, m mod n)的公约数一致,其最大公约数也必然相等。得证

从上述的证明过程中,也不难看出辗转相减法的影子。实际上辗转相除法和辗转相减法的理论基础是相同的,只是实现的方式不同罢了。同时由于辗转相除法能通过一次取模完成多次减法的操作,所以其运算效率较辗转相减法更高。

下面给出算法实现

// 欧几里得算法——循环实现
public static long gcd(long m, long n) {while (n != 0) {long rem = m;m = n;n = rem % n;}return m;
}

对于欧几里得算法的公式而言,使用递归实现可以使得代码更为简洁,所以这里也给出递归实现

// 欧几里得算法——递归实现
public static long gcd(long m, long n) {if (n == 0)return m;return gcd(n, m % n);}

实际上这里的递归实现还可以通过引入三目运算符进行进一步的缩减,代码如下

// 欧几里得算法——单行递归实现
public static long gcd(long m, long n) {return n == 0 ? m : gcd(n, m % n);
}

Stein算法

Stein算法是一种计算两个数最大公约数的算法,是针对欧几里德算法在对大整数进行运算时,需要试商导致增加运算时间的缺陷而提出的改进算法。

在欧几里德算法中,有个核心就是进行取模操作。对于32位或者64位的整数而言,取模操作或者除法操作耗费的时间或许还可以接受。但是对于更大的素数,这样的计算过程就不得不由用户来设计,为了计算两个超过64位的整数的模,用户也许不得不采用类似于多位数除法手算过程中的试商法(可以百度“高位试商法”),这个过程不但复杂,而且消耗了很多CPU时间。对于现代密码学来说,长度大于128位的素数比比皆是(例如RSA的非对称密钥1024位),所以设计这样的程序迫切希望能够抛弃除法和取模。

由J. Stein 1961年提出的Stein算法很好的解决了欧几里得算法中的这个缺陷,Stein算法只有整数的移位和加减法。

同样,本文在这里简单的对其正确性进行简单说明

为了说明Stein算法的正确性,首先必须注意到以下结论:

gcd(m, m) = m,也就是一个数和其自身的公约数仍是其自身。

gcd(km, kn) = k gcd(m, n),也就是最大公约数运算和倍乘运算可以交换。特殊地,当k=2时,说明两个偶数的最大公约数必然能被2整除。

当k与b互质,gcd(km, n)=gcd(m, n),也就是约掉两个数中只有其中一个含有的因子不影响最大公约数。特殊地,当k=2时,说明计算一个偶数和一个奇数的最大公约数时,可以先将偶数除以2。

下面给出算法步骤

1.如果 m = n ,那么 m(或n)*k 是最大公约数,算法结束

2.如果 m = 0 ,n 是最大公约数,算法结束

3.如果 n = 0 ,m 是最大公约数,算法结束

4.如果 m 和 n 都是偶数,则 m /= 2,n /= 2,k *= 2

5.如果 m 是偶数,n 不是偶数,则 m /= 2

6.如果 n 是偶数,m 不是偶数,则 n /= 2

7.如果 m 和 n 都不是偶数,则 m =|m - n|,n = min(m, n)

下面给出Stein算法的代码实现

// Stein算法——递归实现
public static long gcd(long m, long n) {if (m == 0)return n;if (n == 0)return m;if ((m & 1) == 0 && (n & 1) == 0)return 2 * gcd(m >> 1, n >> 1);else if ((m & 1) == 0)return gcd(m >> 1, n);else if ((n & 1) == 0)return gcd(m, n >> 1);elsereturn gcd(Math.abs(m - n), Math.min(m, n));
}

当然,相对来说使用循环实现,开销会更小一些。所以这里也给出Stein算法的循环实现

// Stein算法——循环实现
public static long gcd(long m, long n) {long k = 0;while (m != n) {if (m == 0)return n;if (n == 0)return m;if ((m & 1) == 0 && (n & 1) == 0) {m >>= 1;n >>= 1;k += 1;} else if ((m & 1) == 0){m >>= 1;} else if ((n & 1) == 0){n >>= 1;} else {long tmp = Math.abs(m - n);n = Math.min(m, n);m = tmp;}}return m << k;
}

lcm最小公倍数

根据最小公倍数的计算公式lcm(a, b) = a * b / gcd(a, b);给出lcm的实现代码

public static long lcm(long m, long n) {return m * n / gcd(m, n);
}

算法比较

辗转相除法(欧几里得算法)和辗转相减法(更相减损法)相比,本质是相同的,但是由于辗转相除法一次取模操作相当于多次减法,所以对于规模较大的两个数而言,使用辗转相除法效率更高。

欧几里得算法与Stein算法相比,思路简单且实现代码量少,相对来说更加方便。但是对于数据长度较大的特殊情况时,使用Setin算法能够规避进行除法和取模操作,而通过位运算来替代。算法代码量更大,但是效率相对来说更高。

在实际引用中,不推荐使用穷举法和分解质因数法以及更相减损法,虽然简单易理解,但开销相对较大。

以上内容,挂一漏万。如有缺漏,欢迎指正。

最大公约数(Greatest Common Divisor)【算法及实现】相关推荐

  1. 最大公约数(Greatest Common Divisor)

    两个数的最大公约数.一个典型的解决方案是欧几里德,叫欧几里德算法. 原理:(m,n)代表m和nGCD,和m>n.然后,(m,n)=(n,m%n)=.....直到余数为0. 码如下面: publi ...

  2. 欧几里得算法原理推导及C语言实现求解最大公约数(greatest common divisor)

    1.简介 欧几里得算法(Euclidean algorithm)又名辗转相除法,是迄今为止已知的最古老的算法,距今已有两千多年,该方法可用于快速计算两个数字的最大公约数. 2.定理 a 和 b 的最大 ...

  3. Compute the Greatest Common Divisor of Two Integers using Sieve of Eratosthenes.

    用埃拉托色尼筛选法计算两个整数的最大公约数 最近在回顾算法,会相继贴一些自己写的代码,希望在分享的同时,能够得到观看者的指教,以求共同进步. 以下为我写的程序,运行环境:Dev-C++ 5.4.0. ...

  4. 最大公约数的四种算法

    一.题目分析 运行最大公约数的常用算法,并进行程序的调式与测试,要求程序设计风格良好,并添加异常处理模块(如输入非法等).用四种方法进行运算. 1.辗转相除法: 其算法过程为:设两数为a,b设其中a ...

  5. 【CF1025B】 Weakened Common Divisor

    题目 题目描述 与 GCDGCD (最大公约数)类似,我们引进 WCDWCD (弱公约数)的概念, WCDWCD 的定义如下: 给出几对数 \left( a_1,b_1 \right) ,\left( ...

  6. CF1025B Weakened Common Divisor

    题目描述: During the research on properties of the greatest common divisor (GCD) of a set of numbers, Il ...

  7. cf----2019-10-03(Minimum Value Rectangle,Plasticine zebra,Weakened Common Divisor)

    城市黎明的灯火,总有光环在陨落,模仿者一个又一个,无人问津的角色,你选择去崇拜谁呢,怨恨谁呢? You have nn sticks of the given lengths. Your task i ...

  8. c语言求a b 最大公约数和最小公倍数,常见算法:C语言求最小公倍数和最大公约数三种算法...

    最小公倍数:数论中的一种概念,两个整数公有的倍数成为他们的公倍数,当中一个最小的公倍数是他们的最小公倍数,相同地,若干个整数公有的倍数中最小的正整数称为它们的最小公倍数,维基百科:定义点击打开链接 求 ...

  9. ZOJ 2432 Greatest Common Increasing Subsequence(最长公共上升子序列+路径打印)

    Greatest Common Increasing Subsequence 题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problem ...

最新文章

  1. 程序员缺乏经验的 7 种表现!
  2. 城市是否可以坐车到达问题
  3. 合理持仓 静待市场方向选择
  4. 架构师成长系列 | 云原生时代的 DevOps 之道
  5. 北斗导航 | 基于RTK的GNSS与多源融合定位技术发展与挑战
  6. FireBug 调试JS入门 —如何调试JS
  7. 受控组件和非受控组件
  8. MyCat分布式数据库集群架构工作笔记0013---高可用_Mycat双主双从复制配置上
  9. 不解禁administrator账号的情况下以管理员身份运行bat文件
  10. 使用 json-server 作为 mock 数据
  11. OpenCart多图片拖放式上传管理器
  12. 什么叫侧面指纹识别_正面背面侧面 你手机的指纹识别长在哪?
  13. 统计学(贾俊平《第七版》) 导论部分
  14. 局域网文档服务器搭建,局域网服务器的搭建.pdf
  15. 【python中级】linux系统获得计算机网卡流量
  16. JavaFX 2 Dialogs
  17. 在苹果Macbook Pro上安装Windows 7
  18. GoLang之使用uber-go/dig进行依赖注入
  19. CityBuilder+DataV制作次世代3D城市大屏,一秒俘获甲方的心!
  20. IIS 访问页面出现500 – 内部服务器错误的解决方案

热门文章

  1. 个税app绑定银行卡
  2. php开发与应用,PHP开发与应用_大作业_模板
  3. Tomochain是如何改变Defi市场现状?Tomo.Finance的挑战
  4. Pandas详解三之Index对象
  5. JNI全流程实例使用总结
  6. ipad pro键盘快捷键
  7. \t\t双开网页游戏
  8. 详解 Flutter State 生命周期
  9. 高低压大电流电源设计面临的挑战及难点
  10. 重装系统后,mysql的安装与恢复数据