GCD Extends_GCD 歐幾里得算法+擴展歐幾里得算法詳解

1.歐幾里得算法:

我們從小學開始老師都會讓我們求解一一種問題,叫做最大公約數,這裏的最大公約數就叫做GCD
當然求解最大公約數的算法也是非常的重要,我們在這裏就引入歐幾里得的算法,也就是著名的輾轉相除法
首先給出定義:
GCD(a,b)=GCD(b,a%b)
在這裏有的人會問a,b到底哪個大才能使對的,其實在這裏我們的a,b是沒有大小之分的,要是硬說影響的話,是有那麼一點影響,但是無關痛癢
首先這裏的生命是最好a>b
但是當a<b的時候,我們會發現a%b=a,也就是說這個算法會自動調換會a>b的狀態,所以我們說這一點影響無關痛癢,不影響算法的本質
證明這裏我們就不在涉及
但是在算法的實現方面我有一些要說的:
from time import *
def gcd_1(a,b):
    if b==0:
        return a
    else:
        return gcd_1(b,a%b)

def gcd_2(a,b):    #一旦數據量差距過大無法應用,但是在數據量相差不多的情況下,還是很優秀的,總而言之不要用,因爲不穩定
    if a==b:
        return a
    elif a>b:
        return gcd_2(a-b,b)
    else:
        return gcd_2(b-a,a)

def gcd_3(a,b):
    if a==b:
        return a
    elif a<b:
        return gcd_3(b,a)
    elif not(a&1) and not(b&1):
        return gcd_3(a>>1,b>>1)<<1
    elif not(a&1) and b&1:
        return gcd_3(a>>1,b)
    elif not(b&1) and a&1:
        return gcd_3(a,b>>1)
    else:
        return gcd_3(a-b,b)

time=clock()
a=eval(input("a:"))
b=eval(input("b:"))
print("gcd_1:%d"%gcd_1(a,b))
print("time gcd_1:%lf"%(clock()-time))
#time=clock()
#print("gcd_2:%d"%gcd_2(a,b))
#print("time gcd_2:%lf"%(clock()-time))
time=clock()
print("gcd_3:%d"%gcd_3(a,b))
print("time gcd_3:%lf"%(clock()-time))
實驗結果:
a:34567890
b:1234567
gcd_1:1
time gcd_1:2.589272
gcd_3:1
time gcd_3:0.032302
很顯然,雖然樸素的GCD寫的很簡潔,但是毫無疑問,樸素的GCD因爲取模運算的限制太過,我們的位運算+更相孫減術(gcd_2)結合的優勢最明顯,在我們的工程項目中最好用gcd_3的寫法

2.Extends_gcd擴展歐幾里得算法:

我們的歐幾里得算法很優秀,但是隻用來求解最大公約數好像屈才了,所以這裏我們對歐幾里得算法進行擴展,我們引入擴展歐幾里得算法
在講解擴展歐幾里得算法之前,我們先來講解個很重要的知識:
1.貝祖定理:
對於任何一組不全是0的兩個數x,y總是滿足
ax+by=gcd(x,y)這是一定成立的(之後我們還要講一下不成立的另一箇中很相近的定理)
這個解是唯一的
但是想要求解出這個等式的整數解確實非常的麻煩,我們需要不斷的嘗試,這是沒有辦法忍受的異常繁瑣的工作
所以說,這時候,爲了快速求解出這個唯一的解,我們就需要用到擴展歐幾里得算法
但是這裏請注意,擴展歐幾里得算法是基於歐幾里得算法的,我們在這裏只是引入了逆推 的過程
逆推導:
aX0+bY0=gcd(a,b)=gcd(b,b%a)
gcd(b,a%b)=bX1+(a%b)Y1
....
gcd(a,0)=aXn+0*Yn=a    上面已經提到過了,最後的狀態一定是a>b的
這時候我們讓Xn=1,Yn=0就可以
逆推導開始:
aX0+bY0=gcd(a,b)=gcd(b,a%b)
gcd(b,a%b)=bX1+(a%b)Y1
gcd(b,a%b)=bX1+(a-b*(a/b))Y1=aY1+b*(X1-a/bY1)=gcd(a,b)
顯然,我們可以得到
X0=Y1
Y0=X1-a/bY1
這就是我們的逆推導關係,我們利用C++的引用的手段,可以輕鬆的實現參數的時時改變,輕而易舉實現這個算法
但是小心一點,我們的逆推導的過程是建立在
ax+by=gcd(a,b)上的,也就是說擴展歐幾里得算法求出來的解是在等號右邊是gcd(a,b)的解,這就引出了我們下面的問題

擴展歐幾里得算法的應用:
1.求解不定方程:
ax+by=c
是不是看上去和我們上面的貝祖定理非常的相似?沒錯這時候我們利用擴展歐幾里得求出來ax+by=gcd(a,b)的話,如果這時候c是gcd(a,b)的整數倍的話,我們擴展歐幾里得算法得到的解都乘上c/gcd(a,b)得到的就是這個不定方程的解,但是,如果c不是gcd(a,b)的整數倍,說明這個不定方程沒有解
爲什麼?
因爲我們的ax+by=gcd(a,b)如果有解的條件就是gcd(a,b)是gcd(a,b)的1倍,但是如果沒有說明我們沒有辦法分解出這個ax+by=gcd(a,b)的式子,那麼自然,gcd(a,b)的倍數依舊沒有解

2.求解線性同餘方程:
我們將ax=b(mod n)(ps:那個等號是三等號,代表同餘)滿足的解稱之爲a模b關於n同餘,這時候我們根據同餘的定義可以得到
n|(ax-b)->ax-b=my->ax-my=b->ax+my=b(m只是一個常數,正負無所謂)
是不是又覺得這個方程眼熟,沒錯還是擴展歐幾里得的不定方程
這時候我們就轉化成了求不定方程的問題了,注意這裏的同餘的結果有很多個
但是這些結果都有一個同性
1.結果總數有gcd(a,n)個
2.自通解以後,每個解相差b/gac(a,n)   //這裏我有一個問題,按照程序角度來說,我們會不會少元素的輸出?

3.求解模逆元:
模擬元的定義,如果
ax=1(mod n)稱x是a的模逆元,這裏的條件是a,n必須互素
爲什麼,我們再轉化一下:
ax+ny=1只有我們的gcd(a,n)=1的時候纔有解,並且解唯一,gcd(a,n)=1的意思就是互素
所以模逆元存在的條件是a,n必須互素
性質是模逆元唯一
轉化一下,這時候我們還是要求一個不定方程

綜上,我們的利用歐幾里得的問題始終就是求一個不定方程,只不過是不定方程的等號的右邊的值不斷的改變罷了,只有等號右邊的值是gcd的整數倍纔有解,有gcd個解,否則沒有解

3.Code:

#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"

using namespace std;

int gcd(int a,int b)   //GCD
{
	return b?gcd(b,a%b):a;
}

int Extends_gcd(int a,int b,int& x,int& y)   //擴展歐幾里得算法 
{
	if(b==0)
	{
		x=1;
		y=0;
		return a;
	}
	int g=Extends_gcd(b,a%b,x,y);
	int t=x;
	x=y;
	y=t-(a/b)*y;
	return g; 
} 

int main()
{
	int x;
	int y;
	int a,b,c;
	scanf("%d%d%d",&a,&b,&c);
	int g=Extends_gcd(a,b,x,y);
	printf("GCD:%d\n",g);
	//以下是求出解得,必須要乘倍數,是因爲我們當時算的基礎以gcd,但是實際上方程的右邊是gcd的倍數(有解的話),所以我們要先乘上一個倍數 ,纔是真正的解 
	if(c%g) printf("No solution\n");
	else printf("%d*%d+%d*%d=%d\n",a,x*(c/g),b,y*(c/g),c);
	return 0;
} 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章