浅谈辗转相除法

写在前面

本文主要包含辗转相除法正确性证明,以及时间复杂度证明。已经懂了的老铁可以右滑退出了(或者拉倒最下面点亮小手)~

如何求最大公约数

给定两个正整数,求解最大公约数,代码很简单,比如直接使用__gcd

int gcd = __gcd(a, b);

或者撸一个辗转相除法,像下面这样:

int gcd(int a, int b) {
  return b ? gcd(b, a%b) : a;
}

代码很简单,不过还是应该掌握它的证明过程时间复杂度~

辗转相除法是什么

辗转相除法是用来求两个正整数最大公约数的算法。古希腊数学家欧几里得在其著作《The Elements》中最早描述了这种算法, 所以也被称为欧几里得算法

辗转相除法的正确性依赖于下述定理:

两个整数的最大公约数等于其中较小的数两数相除余数的最大公约数。

以求 46 与 28 的最大公约数为例:

  • 第一次迭代, a = 44, b = 28, 较小数为 28, 余数为 16。
  • 第二次迭代, a = 28, b = 16, 较小数为 16, 余数为 12。
  • 第三次迭代, a = 16, b = 12, 较小数为 12, 余数为 4。
  • 第四次迭代, a = 12, b = 4, 较小数为 4, 余数为 0。
  • 第五次迭代, a = 4, b = 0, 因为0能被任何整数整除,易得4和0的最大公约数为4,算法结束。

观察每次迭代的a 和 b, 不难发现, < 44 , 28 > , < 28 , 16 > , < 16 , 12 > , < 12 , 4 > <44,28>, <28,16>, <16,12>, <12, 4> <44,28>,<28,16>,<16,12>,<12,4>的最大公约数都是4。即上述定理所说的,两个整数的最大公约数等于其中较小的数两数相除余数的最大公约数。

正确性证明

设有两个整数 x, y, 有 x > y。x 和 y 的最大公约数为 d,x除以y的余数为 r。

为了描述方便,不妨假设 x 和 y 均为非负整数。这并不影响算法的结果,因为是求最大公约数,两个负数的最大公约数和其绝对值的最大公约数是相等的。

要证明该算法的正确性,无非就是要证明:如果 x 和 y 的最大公约数为 d,那么 y 和 r 的最大公约数也是 d。 接下来对该结论进行证明。

因为 d 是 x 和 y 的公约数,所以 x, y, r 可借由 d 表示为
{ x = m ∗ d y = n ∗ d r = ( m − k ∗ n ) ∗ d \left\{ \begin{array}{c} x = m*d \\ y = n*d \\ r = (m-k*n)*d \\ \end{array}\right. x=mdy=ndr=(mkn)d

其中,k 为 ⌊ m / n ⌋ \lfloor m/n \rfloor m/n。而且,m 与 n 是互质的,这个很好证明:如果 m 和 n 不互质,则存在整数 p>1 且 p 能整除 m 和 n,则 m 和 n 的最大公约数应大于 d,这与 d 的定义相悖。

不难看出,r 也能被 d 整除,即 d 是 y 和 r 的公约数

又因为,m 和 n 互质,所以不存在整数 q > 1 能同时整除 n 和 ( m − k ∗ n ) (m-k*n) (mkn),所以 d 是 y 和 r 的最大公约数

综上所述,如果 x 和 y 的最大公约数为 d,那么 y 和 r 的最大公约数也是 d, 所以辗转相除法,可以正确求得 x 和 y 的最大公约数。

时间复杂度

设有两个正整数 x 和 y,不妨假设 x > y。另设 r = x%y。

不难发现, r ≤ ⌊ x / 2 ⌋ r \le \lfloor x/2 \rfloor rx/2。证明如下:

  • y ≤ ⌊ x / 2 ⌋ y \le \lfloor x/2 \rfloor yx/2 时,由取余运算的性质可知,$ r \lt y \le \lfloor x/2 \rfloor$
  • ⌊ x / 2 ⌋ < y ≤ x \lfloor x/2 \rfloor \lt y \le x x/2<yx时,取余运算退化为减法运算, r = x − y ≤ ⌊ x / 2 ⌋ r = x-y \le \lfloor x/2 \rfloor r=xyx/2

换言之,每经过两次区域的操作,新的 x ‘ , y ‘ x^`, y^` x,y 最差也会变为原来的一半 ,所以时间复杂度的上限为 Θ ( l g x ) \Theta(lg x) Θ(lgx)

至于下限如何证明,我还没想出来,欢迎老铁后台留言指点迷津~

取余运算与取模运算

既然本文中用到了运算符 %,那就多说几句。

% 在 C++ 中代表取余运算,在 Python 中代表取模运算。那两种运算有何不同呢?

设有两个整数 x 和 y,两种运算的过程都是:

  • c = x/y
  • r = x - c*y

不同之处在于第一步的取整:取余运算在取c的值时,向0方向舍入。取模运算是向 − ∞ -∞ 方向舍入。

举个例子,x = -7,y = 4:

  • 第一步,求 c 的值:

    • 求模:c = -2(向无穷小方向舍入)
    • 求余: c = -1(向0方向舍入);
  • 第二步,求 r 的值:

    • 求模:r = 1
    • 求余:r = -3

总结一下:

  • 当x和y符号一致时,两种运算结果一致。
  • 当符号不一致时,两种运算结果不一致。求模运算结果的符号和y一致,求余运算结果的符号和x一致。

数组辗转相除法通常指的是辗转相除法(也称欧几里得算法)在处理数组中的元素时的应用。这个算法主要用于计算两个正整数a和b的最大公约数(GCD)。基本思想是:两个正整数a和b(a>b),它们的最大公约数等于较小的数b和两数相除余数c的最大公约数。 在C++中,如果要对数组中的所有元素应用辗转相除法来找出它们的最大公约数,可以按照以下步骤操作: 1. 假设数组中的第一个元素是最大公约数。 2. 依次将数组中的下一个元素与当前已知的最大公约数进行辗转相除法计算,更新最大公约数。 3. 对数组中每个元素重复此过程,直到遍历完数组中的所有元素。 下面是使用C++实现数组中所有元素辗转相除法的一个示例代码: ```cpp #include <iostream> using namespace std; // 函数声明 int gcd(int a, int b); int findGCDofArray(int arr[], int n); int main() { int arr[] = {12, 18, 24, 30}; // 示例数组 int n = sizeof(arr)/sizeof(arr[0]); // 数组中元素的个数 cout << "数组元素的最大公约数是: " << findGCDofArray(arr, n) << endl; return 0; } // 辗转相除法计算两个整数的最大公约数 int gcd(int a, int b) { while (b != 0) { int c = a % b; a = b; b = c; } return a; } // 找出数组中所有元素的最大公约数 int findGCDofArray(int arr[], int n) { int result = arr[0]; for (int i = 1; i < n; i++) { result = gcd(result, arr[i]); if (result == 1) { // 如果最大公约数为1,则无需继续计算 return 1; } } return result; } ``` 上述代码中,`gcd`函数用于计算两个整数的最大公约数,`findGCDofArray`函数则用于找出数组中所有元素的最大公约数。在`main`函数中,我们创建了一个示例数组并调用`findGCDofArray`函数来计算并打印其元素的最大公约数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值