已知两个整数a,b,求a,b的最大公约数,我们有如下的方法:
1.循环i从mind(a,b)~1,第一个既能被a和b整除的i就是a和b的最大公约数。
2.辗转相减法
3.辗转相除法,即欧几里得。
算法1:暴力求解
int gcd(int a,int b)
{
int ans;
for(int i = min(a,b); i > 0; i--)
{
if(a%i==0 && b%i==0)
{
ans = i;
break;
}
}
return ans;
}
算法2:辗转相减法
int gcd(int m,int n)
{
if (m==n) return m;
if(m
辗转相减法是很不错的。
算法3:辗转相除法
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
虽然天天用辗转相除法,但是确没有证明过为什么这样是对的。
证明欧几里得算法:
假如我们要求整数a和b的最大公约数。无特殊声明,以下出现的小写字母都代表整数,
设 q = a/b(在此除法指整除) 是商,r = a%b 为余数,d = gcd(a,b),即a、b的最大公约数为d。
则 a = b*q + r
由于d是a和b的最大公约数,因此一定存在整数c1,c2使得a = c1*d , b = c2*d
因为a = b*q + r ,
所以 r = a - b*q
= c1*d - c2*d*q
= d*(c1-c2*q)
由于c1,c2,q全都是整数,所以c1-c2*q也是整数,从上面的式子我们可以看到r / d = c1-c2*q
由此可知d也是r的因子,因为d是b的因子,则d是b和r的公因子,但是现在需要证明d到底
是不是b和r的最大公因子呢?
继续往下证明。假设c是b和r的任一公因子,则b是c的倍数,r也是c的倍数,从a = b*q+r
可以看出a必然是c的倍数,所以c也是a,b的因子,但是a,b的最大公因子是d,所以c<=d.
又因为c是b和r的任一公因子,因此c可以是b和r的最大公因子,c又是小于等于d的。且
d是b和r的公因子,所以d就是b和r的最大公因子。
因此d = gcd(b,r).
综上所述: d = gcd(a,b) = gcd(b,r) = gcd(b,a%b)
上面的式子表示的就是一个递归的过程,递归结束的条件是0,这时最后的余数为0.
欧几里得算法推论:扩展欧几里得
若d = gcd(a,b),则有x和y使得ax+by = d
这一点我们可以通过逆推欧几里得算法得到。
我们知道欧几里得算法执行到最后:
a = gcd(a,b) = d, b = 0
则存在 x1 = 1 , y1 = (任意数,因为b为0,不过我们通常取0) ,满足a*x1 + b*y1 = d 成立
由于gcd(a,b) = gcd(b,r)
我们知道最后一步得到:
b*x1 + r*y1 = d;
r = a%b 得到下面的式子
b*x1 + a%b*y1 = d;
b*x1 + (a-a/b*b)*y1 = d
b*x1 + a*y1 - (a/b)*y1*b = d
a*y1 + (x1-(a/b)*y1)*b = d
解得:
x2 = y1 y2 = x1 - (a/b)*y1;
同理我们可以继续网上推,可以知道必定是存在解x,y使得ax+by=gcd(a,b)。
证必。
算法实现:在欧几里得算法上做小小的改进
int d,x,y;
///扩展欧几里得。
void extend_gcd(int a,int b)
{
if(b == 0)
{
x = 1;
y = 0;
d = a;
return;
}
extend_gcd(b,a%b);
int temp = x; ///先暂时存放一下x
x = y;
y = temp-a/b*y;
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
extend_gcd(n,m);
printf("%d %d %d",d,x,y);
}
return 0;
}
本文部分证明参考自《基础数论》这本书。