欧几里德算法
欧几里德算法又称为辗转相除法,用于计算两个非负整数的最大公因数。其伪代码如下:
gcd(a, b) //要求a>=b if(b == 0) return a return gcd(b, a % b)
结果的正确性源于a与b的最大公约数c也是a%b的公共约数。原因很简单,a%b=a-kb=ic-kjc=(i-kj)c,故a%b能整除c。设p为b和a%b的最大公约数,由于a=kb+a%b=xkp+yp=(xk+y)p,因此p能被a整除,因此p也是a与b的最大公约数,因此可以保证c>=p>=c,故p=c。
再不考虑递归的情况下,gcd的时间复杂度为O(1),而每次递归调用都会使得两个传入参数a与b中较大者减少一半以上(a%b<=min{a-b,b}),因此递归最多发生O(log2(a)+log2(b))=O(log2(ab))次,总的时间复杂度为O(log2(ab)),空间复杂度主要集中在递归,同样也是O(log2(ab))。
应用
场景1
对于自然数a与b,有时候我们需要求解能使得x*(a/b)为整数的最小自然数x。我们可以将式子改写为x*((a/c)/(b/c)),其中c是a与b的最大公约数,由于a/c与b/c没有公约数,即二者互质,因此由算术基本定理知x能整除b/c,即x可以取值b/c,2b/c,...。当我们选取b/c作为x值时,式子的值为x*(a/b)=a/c,为整数,因此b/c即为我们要求的值。
场景2
设z是x与y的最大公约数,要求让ax+by=z成立的某组可能的a与b的取值,也可以通过欧几里德算法实现。
若x与y中有至少一者为0时,我们只需要取非0元素的系数为1,就可以得到一组解。而当x=y时只需要令a,b中任意一个为1另外一个为0即可得到所求值。之后均认为x>y>0。若我们已知ny+m(x%c)=c成立,则进行如下推导:$$ ny+m\left(x\%y\right)=z $$ $$ \Rightarrow ny+m\left(x-y\cdot\lfloor\frac{x}{y}\rfloor\right)=z $$ $$ \Rightarrow m\cdot x+\left(n-m\cdot\lfloor\frac{x}{y}\rfloor\right)\cdot y=z $$因此我们就得到了一组可行的a与b的值。
到此我们可以使用递归的思路求解a与b:
coe(x, y) //认为x>y if(y == 0) return (1, 0) n, m = coe(y, x % y) return (m, n - m * floor(x / y))
对于已知的一组式子ax+by=z,其中a,x,b,y,z均已知且z是x与y的公约数,希望找到另外一对n,m使得nx+my=c成立且n是所有符合值中最小自然数,即我们希望锐化a的取值。首先我们分别令等式左右两边除以z,此时式子形如np+mq=1,其中p与q互质。而我们的目标是通过修改n和m的值使得等式成立且n为最小自然数,因此我们将n与m的变更值记作u与v(这里认为n尚未达到目标结果,即u与v均非0),则可得up+vq=0。由于p与q互质,因此u能整除q且v能整除p,u的值最小允许取-q和q,表示对n最小幅度的波动。我们可以按照n现有符号对n不断加上q或-q,直到n成为最小的可行自然数。这里可以直接写作n%q。因此我们知道了n%(y/z)是使得式子ax+by=z满足的最小自然数。
要求ax+by=cz满足的一组a与b的取值,且c是给定整数,可以先使用上面的方法取一对可行解n,m使得nx+my=z成立,之后两端均乘上c得到cnx+cmy=cz,就得到一组可行系数。