【演算法拾遺】最大公約數

蘭亭風雨發表於2014-06-05

轉載請註明出處:http://blog.csdn.net/ns_code/article/details/28505569


序言

    求兩個正整數的最大公約數是一個很古老且很基本的問題,歐幾里得在其著作《幾何原本》中給出了高效的解法——輾轉相除法,也叫做歐幾里得演算法。下面我們來看下求最大公約數的一些方法。

方法一

    我們先來看歐幾里得的輾轉相除法。原理很簡單,假設用f(x,y)表示x和y的最大公約數,我們令x>y,則有x=ky+b,如果一個數能夠同時整除x和y,則必能同時整除b和y,而能夠同時整除b和y的數也必能同時整除x和y,即x和y的公約數與b和y的公約數相同,因此二者的最大公約數也相同,則有f(x,y)=f(y,x%y),一直輾轉相除,最終當其中一個為0時,剩下的另一個就是二者的最大公約數。一個例子如下所示:

f(42,30) = f(30,12) = f(12,6) = f(6,0) = 6

    輾轉相除法的程式碼實現如下:

/*
歐幾里得演算法,輾轉相除求最大公約數
*/
int MaxYue1(int a,int b)
{
	//在輾轉相除之前,確保a比b大
	if(a<b)
	{
		int temp = a;
		a = b;
		b = temp;
	}
	//輾轉相除法球最大公約數
	while(b!=0)
	{
		int temp = a%b;
		a = b;
		b = temp;
	}
	return a;
}
    該方法用到了取模運算,編譯器在執行取模運算時,會轉化為相應的除法運算,如:x%y = x - (x%y)*y,除法運算的開銷很大,尤其對於大的整數。

方法二

    為了避免除法運算帶來的大的開銷,我們可以用輾轉相減法來實現,同樣利用的原理如下:如果一個數能夠同時整除x和y,則它必能同時整除x-y和y,因此最大公約數f(x,y) = f(y,x-y),注意要保證左邊的數大於右邊的數,如果小於,則將二者進行交換。

    輾轉相減法的實現程式碼如下:

/*
輾轉相減法求最大公約數
*/
int MaxYue2(int a,int b)
{
	if(a<b)
		return MaxYue2(b,a);
	if(b==0)
		return a;
	else
		return MaxYue2(b,a-b);
}
    該方法雖然避免了除法操縱帶來的大的時間開銷,但是對於大的整數,也要進行很多次的相減操作,尤其遇到兩個數相差很大的情況,比如f(1000000,1)這樣的情況,迭代相減的次數就會很多。

方法三

    為了減少迭代的次數,我們考慮對上述演算法進行改進,很明顯,我們可以有如下結論:

    1、當x、y都為偶數時,f(x,y) = 2*f(x/2,y/2)

    2、當x為偶數,y為奇數時,f(x,y) = f(x/2,y)

    3、當x為奇數,y為偶數時,f(x,y) = f(x,y/2)  

    4、當x,y多為奇數時,f(x,y) = f(y,x-y)  

    每一次的操作必然是以上四種情況的其中一種,且我們對乘2和除2的操作可以通過移位來完成,效率很高,很明顯這種演算法最壞情況下的時間複雜度為O(log2max(x,y))(以2為底,max(x,y)的對數),很適合對大的整數進行計算。

    這種方法實現的程式碼如下;

/*
改進輾轉相減法
*/
int MaxYue3(int a,int b)
{
	if(a<b)
		return MaxYue3(b,a);
	if(b==0)
		return a;
	else
	{
		if((a&1)==0)	//a為偶數
		{
			if((b&1)==0) //b也為偶數
				return (MaxYue3(a>>1,b>>1)<<1);
			else		//b為奇數
				return MaxYue3(a>>1,b);
		}
		else	//a為奇數
		{
			if((b&1)==0) //b為偶數
				return MaxYue3(a,b>>1);
			else		//b也為奇數
				return MaxYue3(b,a-b);
		}
	}
}


相關文章