辗转相除法

本文内容主要引用自《挑战程序设计竞赛》

1.求最大公约数

  现在给定我们两个整数\(a\)\(b\),要求我们求出两者的最大公约数。最简单的思路就是从\(1\)枚举到\(min(a,b)\),但是效率较低,因此我们就需要一种更快速的算法,即辗转相除法。
  设函数\(gcd(a,b)\)是求取\(a,b\)的最大公约数,假设\(a\)除以\(b\)得到的商和余分别为\(p\)\(q\)。因为有\(a=p*b+q\),因此我们设\(m=gcd(b,p)\),由等式右边可以知道\(m\mid a\)。反之,我们有\(q=a-b*p\),两边同除以\(m\),我们知道得到的同样是一个整数,因此\(m\mid q\)。从而知道\(gcd(b,a\bmod b)=gcd(a,b)\)。不断这样操作下去,由于\(gcd\)函数中的第二个参数总是不断变小,最终会得到\(gcd(a,b)=gcd(c,0)\)。而\(0\)\(c\)的最大公约数是\(c\),所以\(gcd(c,0)=c\),这样就得到了\(gcd(a,b)\)
  辗转相除法的程序实现如下:

代码
int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}

  辗转相除法时间复杂度的粗略估计:假设\(a\)\(b\)是两个自然数,如果\(b>a\),那么我们就会有\(gcd(b,a%b)=gcd(b,a)\),经过一次递归后就变成了\(a>b\),因此我们不妨假设\(a>b\),此时函数就会按照\(gcd(a,b) \to gcd(b,a\bmod b)\to gcd(a\bmod b,b\bmod (a\bmod b))\)这样递归下去。当\(b>a/2\)时我们有\(a\bmod b=a-b<a/2\),当\(b<a/2\)时,则有\(a\bmod b<b<a/2\),于是经过两次递归后,第一个参数会小于原来的一半,所以其时间复杂度在\(O(log(max(a,b))\)以内。

2.拓展欧几里得算法

  现在我们来考虑解决一个方程\(ax+by=1\)。可以发现,如果\(gcd(a,b)!=1\),那么这个方程就是无解的,反之,如果\(gcd(a,b)=1\),就可以通过拓展上面的辗转相除法来求解。通过推广我们知道,一定存在整数对\((x,y)\)使得\(ax+by=gcd(a,b)\),并可以通过相同的算法来解决。
  现设int exgcd(int a,int b,int &x,int &y)是求解该方程的函数,其返回值是\(gcd(a,b)\),与\(gcd\)一样,我们可以递归地定义\(exgcd\)。假设已经求得了\(b\cdot x^\prime+(a\bmod b)y^\prime=gcd(a,b)\)的整数解\(x^\prime\)\(y^\prime\)。再将\(a\bmod b=a-(a/b)\cdot b\)带入就会得到\(a\cdot y^\prime+b\cdot(x^\prime-(a/b)\cdot y^\prime)=gcd(a,b)\)。则当\(b=0\)时,就有\(a\cdot 1+b\cdot 0=a=gcd(a,b)\)。将上述数学公式转换成代码就是:

代码
int exgcd(int a,int b,int &x,int &y)
{
	int d=a;
	if(b==0)
	{
		x=1,y=0;
		return a;
	}
	d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return d;
}
posted @ 2021-04-17 11:35  Daneii  阅读(506)  评论(0)    收藏  举报