写在前面
本文主要包含辗转相除法的正确性证明,以及时间复杂度证明。已经懂了的老铁可以右滑退出了(或者拉倒最下面点亮小手)~
如何求最大公约数
给定两个正整数,求解最大公约数,代码很简单,比如直接使用__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=m∗dy=n∗dr=(m−k∗n)∗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) (m−k∗n),所以 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 r≤⌊x/2⌋。证明如下:
- 当 y ≤ ⌊ x / 2 ⌋ y \le \lfloor x/2 \rfloor y≤⌊x/2⌋ 时,由取余运算的性质可知,$ r \lt y \le \lfloor x/2 \rfloor$
- 当 ⌊ x / 2 ⌋ < y ≤ x \lfloor x/2 \rfloor \lt y \le x ⌊x/2⌋<y≤x时,取余运算退化为减法运算, r = x − y ≤ ⌊ x / 2 ⌋ r = x-y \le \lfloor x/2 \rfloor r=x−y≤⌊x/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一致。