欧几里德算法:(辗转相除法)
求整数a、b (a>b) 的最大公因数(Greatest Common Divisor):
算法内容
假设整数a>=b
- a / b = q1 … r1
- 如果 r1=0,gcd(a,b)=b;
- 如果 r1!=0,b / r1 = q2 … r2;
- 如果 r2=0,gcd(a,b)=r1;
- 如果 r2!=0,r1 / r2 = q3 … r3;
- ……
- 以此往复,直到最后余数为0的除数即为gcd(a,b)
证明
我们假设整数a / b = q … r (a>=b),显然有b>r
转换一下得到:a = bq + r
现在我们假设c为a和b的一个公因数,即 c | a,c | b (意为c是b的一个因子)
于是我们可以设:a= cm; b= cn;
将a,b带到 a = bq + r 里去,得到:cm=cnq+r
移项合并可以得到:c(m-nq) = r,即 c | r
推得:所有a和b的公因子都是b和r的公因子
同理:所有b和r的公因子都是a和b的公因子
于是得到结论:gcd(a,b)=gcd(b,r)=gcd(b,a%b)
code
int gcd(int a,int b)
{
return b==0 ? a : gcd(b,a%b);
}
最小公倍数(Lowest Common Multiple)
因为:
gcd(a,b)*lcm(a,b)=a*b
所以:
lcm(a,b)=a/gcd(a,b)*b;//为了防止a*b溢出,所以先除后乘
扩展欧几里德算法:
输入 a,b ,求整数 x,y 使得 ax+by=gcd(a,b) 成立。
内容+证明:
- 假设
b*x1+(a%b)*y1=gcd(b,a%b)
已经求出(x1,y1) - 由欧几里德算法可知
gcd(a,b)=gcd(b,a%b)
- 所以可以得到
a*x+b*y=b*x1+(a%b)*y1
- 因为
a%b=a-(a/b)*b
- 所以
a*x+b*y=b*x1+(a-(a/b)*b)*y1
- 所以
a*x+b*y=a*y1+b(x1-(a/b)*y1)
- 所以:
x=y1; y=b(x1-(a/b)*y1);
那么显然我们可以借助欧几里德通过递归来求出(x,y)
code:
int x,y;
int exgcd(int a,int b)
{
if(!b)
{
x=1;
y=0;
return a;
}
int d=exgcd(b,a%b);
int tx=y;
int ty=x-(a/b)*y;
x=tx;
y=ty;
return d;
}
部分代码解释:
d=exgcd(b,a%b);
跑完这一步,(x,y)实际上被赋值为(x1,y1),所以我们要通过推得的式子来得到初始我们要求的(x,y)
已知方程ax+by=c的一组解(x0,y0),我们如何求方程通解?
首先,我们先考虑求一组(x0,y0)的相邻的解(x1,y1)
设x1=x0+i
则y1=[ a*(x0+i)-c ] / (-b)=(ax0-c) / (-b) +a*i / (-b)= y-a*i/b
因为我们要求整数解,所以要保证a*i / b
和i
都是整数;又因为我们要求的是相邻解,所以我们要在i是整数的前提下使a*i / b
尽可能小
一、可以分两种情况来考虑:
- 如果a和b互质,那么有且仅有
i=b
能使a*i / b
为整数且最小 - 如果a和b不互质,那么就先使
a*i / b
化简,上下同时除以gcd(a,b),使分子分母互质,即(a/gcd(a,b))*i / b/gcd(a,b)
,这个时候i=b/gcd(a,b)
才能满足条件 - 综合上面两种情况,只有当
i=b/gcd(a,b)
时,(x1,y1)为(x0,y0)的相邻解,即x1=x0+b/gcd(a,b)
y1=y0-a/gcd(a,b)
二、我们要在i是整数的前提下使a*i / b
尽可能小,所以a*i=lcm(a,b)
,所以i=lcm(a,b)/a=a*b/gcd(a,b)/a=b/gcd(a,b)
也就是i=b/gcd(a,b)
,和上面得出的结果是一样的
由于方程是线性的,所以整数解满足线性关系,所以通解为:
x[k]=x0+k*b/gcd(a,b)
y[k]=y0-k*a/gcd(a,b)
另外:
设,a,b,c是任意整数,g=gcd(a,b),方程 ax+by=g 的一组解是(x0,y0),则当c是g的倍数时,ax+by=c 的一组解是(x0c/g,y0c/g);当c不是g的倍数时无整数解。