欧几里德算法 贝祖等式 扩展欧几里德算法
计算机程序设计艺术开篇便提到了“欧几里德”算法,也就是中国的“辗转相除法”。这个是个古老、经典的算法。
说的是:两个正整数 a,b(设a>b)求出他们的最大公约数。
算法步骤:
1. r=a mod b (mod是相除取余数的意思)
2.如果r=0,则b是最大公约数,否则转3
3.a=b ,b=r 转 1
算法的一种证明:
证:
要证欧几里德算法成立,即证: gcd(a,b)=gcd(b,r),其中 gcd是取最大公约数的意思,r=a mod b
下面证 gcd(a,b)=gcd(b,r)
设 c是a,b的最大公约数,即c=gcd(a,b),则有 a=mc,b=nc,其中m,n为正整数,且m,n互为质数
由 r= a mod b可知,r= a- qb 其中,q是正整数,
则 r=a-qb=mc-qnc=(m-qn)c
b=nc,r=(m-qn)c,且n,(m-qn)互质(假设n,m-qn不互质,则n=xd, m-qn=yd 其中x,y,d都是正整数,且d>1
则a=mc=(qx+y)dc, b=xdc,这时a,b 的最大公约数变成dc,与前提矛盾,
所以n ,m-qn一定互质)
则gcd(b,r)=c=gcd(a,b)
得证。
定理2:(由法国数学家拉梅证明)欧几里得算法所需除法次数不超过m和n中较小的那个数的十进制位数的5倍
定理3:欧几里得算法所需除法次数不超过2 log(n+1),其中n为较小的数
由欧几里德算法,衍生出来一个重要的贝祖等式:两数的最大公约数可以用两数的整数倍相加来表示,如21 = gcd(105,252)=5 × 105 + (−2) × 252。
贝祖等式的 非严格证明:
证明:
我们可以将辗转相除法的步骤看成:
a = p(0)b + r(0)
b = p(1)r(0)+r(1)
r(0) = p(2)r(1)+r(2)
r(1) = p(3)r2+r(3)
......
r(n-2)= p(n)r(n-1)+ r(n)
r(n-1)= p(n+1)r(n)+ r(n+1)
r(n) = p(n+2)r(n+1)
当我们做到最后一个式子的时候,我们就知道 gcd(a,b)=r(n+1);
由倒数第二个式子,我们可知:r(n+1) =gcd(a,b)=r(n-1) - p(n+1)r(n),其中r(n)又可以用上面的式子r(n)=r(n-2)-p(n)r(n-1)代替,就这样不断地用上面的式子代替,
到最后我们就可以得到gcd(a,b)关于a,b的一次的关系式。
得证.
现在我们已经知道 存在 x,y使得 ax+by=gcd(a,b)成立了,那么怎样更直接地求x,y呢?
设: a*x1+b*y1=gcd(a,b)
b*x2+(a mod b)*y2=gcd(b,(a mod b))
我们已经知道了:gcd(a,b)=gcd(b,(a mod b))
那么有:a*x1+b*y1=b*x2+(a mod b)*y2=b*x2+(a-a/b*b)*y2=b*x2 +a*y2- a/b*b*y2=a*y2+b*(x2-a/b*y2)
那么我们有:x1=y2 ; y1=x2-a/b*y2, 这样就把求x1,y1的问题,转化为求b*x2+(a mod b)*y2=gcd(a,b)中x2,y2的问题。
这个思路是基于递归的思想的。因为 gcd 不断的递归求解一定会有个时候 b=0,所以递归可以结束。
基于递归实现的C++代码:
#include
<
iostream
>
using namespace std;
int x = 0 ,y = 0 ,gcd = 0 ;
void qq( int , int );
int main()
{
int a,b;
cin >> a >> b;//输入的时候保证a>b,a,b是正整数就好了就好了,我这里没再检查a,b的有效性。
qq(a,b);
cout << x << " * " << a << " + " << y << " * " << b << " = " << x * a + y * b << " = " << gcd << endl;
return 0 ;
}
void qq( int a, int b)
{
if ( 0 == b)
{
x = 1 ;
y = 0 ;
gcd = a;
return ;
}
else
{
qq(b,a % b);
int temp = x;
x = y;
y = temp - a / b * y;
return ;
}
}
using namespace std;
int x = 0 ,y = 0 ,gcd = 0 ;
void qq( int , int );
int main()
{
int a,b;
cin >> a >> b;//输入的时候保证a>b,a,b是正整数就好了就好了,我这里没再检查a,b的有效性。
qq(a,b);
cout << x << " * " << a << " + " << y << " * " << b << " = " << x * a + y * b << " = " << gcd << endl;
return 0 ;
}
void qq( int a, int b)
{
if ( 0 == b)
{
x = 1 ;
y = 0 ;
gcd = a;
return ;
}
else
{
qq(b,a % b);
int temp = x;
x = y;
y = temp - a / b * y;
return ;
}
}
循环版本:
#include
<
iostream
>
using namespace std;
void qq( int , int );
int main()
{
int a,b;
cin >> a >> b;
qq(a,b);
return 0 ;
}
void qq( int a, int b)
{
int x = 1 ,y = 0 ,gcd = 0 ; //
int temp[ 100 ][ 2 ]; //记录过程中的a,b
int counter = 0 ;
while (b != 0 )
{
temp[counter][ 0 ] = a;
temp[counter][ 1 ] = b;
counter ++ ;
int tempnum = a;
a = b;
b = tempnum % b;
}
gcd = a;
cout << gcd << endl;
for ( int i =-- counter;i >= 0 ;i -- )
{
int tempnum = x;
x = y;
y = tempnum - temp[i][ 0 ] / temp[i][ 1 ] * y;
}
cout << x << " * " << temp[ 0 ][ 0 ] << " + " << y << " * " << temp[ 0 ][ 1 ] << " = " << x * temp[ 0 ][ 0 ] + y * temp[ 0 ][ 1 ] << " = " << gcd << endl;
}
以上求解x,y的过程我们不妨把它称为“扩展欧几里德算法”,当然我这种说法很不好,为什么呢?
using namespace std;
void qq( int , int );
int main()
{
int a,b;
cin >> a >> b;
qq(a,b);
return 0 ;
}
void qq( int a, int b)
{
int x = 1 ,y = 0 ,gcd = 0 ; //
int temp[ 100 ][ 2 ]; //记录过程中的a,b
int counter = 0 ;
while (b != 0 )
{
temp[counter][ 0 ] = a;
temp[counter][ 1 ] = b;
counter ++ ;
int tempnum = a;
a = b;
b = tempnum % b;
}
gcd = a;
cout << gcd << endl;
for ( int i =-- counter;i >= 0 ;i -- )
{
int tempnum = x;
x = y;
y = tempnum - temp[i][ 0 ] / temp[i][ 1 ] * y;
}
cout << x << " * " << temp[ 0 ][ 0 ] << " + " << y << " * " << temp[ 0 ][ 1 ] << " = " << x * temp[ 0 ][ 0 ] + y * temp[ 0 ][ 1 ] << " = " << gcd << endl;
}
在《计算机程序设计艺术》第一卷 1.2 数学准备 这个小节里面,Knuth先生给出了一个十分优美的“扩展欧几里德算法”
如下:
扩展的欧几里德算法:给定两个正整数m和n,我们计算它们的最大公约数和两个整数a和b,使am+bn=d;
E1:【初始化】a0=b=1; a=b0=0; c=m;d=n;
E2:【除】q=c/d;r=c%d
E3:【余数为0?】如果 r=0;算法终止,在这种情况下,我们如愿地有:am+bn=d;
E4:【循环】c=d; d=r; t=a0; a0=a; a=t-qa; t=b0; b0=b; b=t-qb, 返回E2
END;
关于这个算法,Knuth先生也给出了优美的证明,我这里不再赘言;
下面给出实现代码:
#include
<
iostream
>
using namespace std;
void qq( int , int );
int main()
{
int m,n;
cin >> m >> n;//这里同样没有检查m、n有效性,自己保证m>n,都是正整数
qq(m,n);
return 0 ;
}
void qq( int m, int n)
{
int a = 0 ,b = 1 ,a0 = 1 ,b0 = 0 ;
int c = m,d = n;
int q = c / d;
int r = c % d;
while (r != 0 )
{
c = d;
d = r;
int t = a0;
a0 = a;
a = t - q * a;
t = b0;
b0 = b;
b = t - q * b;
q = c / d;
r = c % d;
}
cout << " a= " << a << " b= " << b << " d= " << d << endl;
}
我们可以看到这个算法的实现起来非常的简洁、明了!
using namespace std;
void qq( int , int );
int main()
{
int m,n;
cin >> m >> n;//这里同样没有检查m、n有效性,自己保证m>n,都是正整数
qq(m,n);
return 0 ;
}
void qq( int m, int n)
{
int a = 0 ,b = 1 ,a0 = 1 ,b0 = 0 ;
int c = m,d = n;
int q = c / d;
int r = c % d;
while (r != 0 )
{
c = d;
d = r;
int t = a0;
a0 = a;
a = t - q * a;
t = b0;
b0 = b;
b = t - q * b;
q = c / d;
r = c % d;
}
cout << " a= " << a << " b= " << b << " d= " << d << endl;
}
求解同样的问题,算法不同,会有相当大的差别。
程序=算法+数据结构
<全文完>