本文為joshua317原創文章,轉載請注明:轉載自joshua317博客 https://www.joshua317.com/article/140
一、最大公約數說明
最大公約數,也稱最大公因數、最大公因子,指兩個或多個整數共有約數中最大的一個。
如果有一個自然數a能被自然數b整除,則稱a為b的倍數,b為a的約數。約數和倍數都表示一個整數與另一個整數的關系,不能單獨存在。比如,只能說8是某數的倍數,2是某數的約數,而不能孤立地說8是倍數,2是約數。
幾個自然數公有的約數,叫做這幾個自然數的公約數。其中最大的一個,叫做這幾個數的最大公約數。比如,12、16的公約數有1、2、4,其中最大的一個是4,4是12與16的最大公約數,一般記為(12,16)=4。12、15、18的最大公約數是3,記為(12,15,18)=3。
二、實現最大公約數的算法
2.1 輾轉相除法,又稱歐幾里德算法
輾轉相除法又稱歐幾里德算法,是用來求兩個正整數最大公約數的算法。它是古希臘數學家歐幾里得在其著作《The Elements》中最早描述了這種算法,所以被命名為歐幾里得算法。
定理:兩個正整數的最大公約數等於其中較小的那個數和兩數相除余數的最大公約數。最大公約數(Greatest Common Divisor)縮寫為GCD。
gcd(a,b) = gcd(b,a mod b) (不妨設a>b 且r=a mod b ,r不為0)
歐幾里得的輾轉相除算法是計算兩個自然數最大公約數的傳統算法,對於多個自然數可以執行多次輾轉相除法來得到最大公約數。輾轉相除法的執行過程如下:
(1)對於已知兩自然數a、b,假設a>b;
(2)計算a除以b,將得到的余數記為r;
(3)如果r=0,則b為求得的最大公約數,否則執行下面一步;
(4)將b的值保存到a中,將r的值保存到b中,
重復執行步驟(2)和(3),直到r=0,便得到最大公約數。
流程圖如下:
2.2 Stein算法
Stein算法是一種計算兩個數最大公約數的算法,是針對歐幾里德算法在對大整數進行運算時,需要試商導致增加運算時間的缺陷而提出的改進算法。
歐幾里德算法是計算兩個數最大公約數的傳統算法,無論從理論還是從實際效率上都是很好的。但是卻有一個致命的缺陷,這個缺陷在素數比較小的時候一般是感覺不到的,只有在大素數時才會顯現出來。一般實際應用中的整數很少會超過64位(當然已經允許128位了),對於這樣的整數,計算兩個數之間的模是很簡單的。對於字長為32位的平台,計算兩個不超過32位的整數的模,只需要一個指令周期,而計算64位以下的整數模,也不過幾個周期而已。但是對於更大的素數,這樣的計算過程就不得不由用戶來設計,為了計算兩個超過64位的整數的模,用戶也許不得不采用類似於多位數除法手算過程中的試商法,這個過程不但復雜,而且消耗了很多CPU時間。致使輾轉相除法效率變低。對於現代密碼算法,要求計算128位以上的素數的情況比比皆是,設計這樣的程序迫切希望能夠拋棄除法和取模。
接下來我們來分析下Stein算法的原理:
gcd是greatest common divisor(最大公約數)的縮寫,用gcd( x,y ) 表示x和y的最大公約數。然后有一個事實需要了解:一個奇數的所有約數都是奇數。研究一下最大公約數的性質,我們發現, gcd( k*x,k*y ) = k*gcd( x,y )
。說它好,是因為它非常符合化小的思想。我們取 k=2 ,則有 gcd( 2*x,2*y ) = 2 * gcd( x,y )
。這使我們很快聯想到將兩個偶數化小的方法。那么一奇一個偶以及兩個奇數的情況如何化小呢?
一奇一偶:
設有2x和y兩個數,其中y為奇數。因為y的所有約數都是奇數,所以 a = gcd(2*x,y)
是奇數。根據2*x
是個偶數不難聯想到,a應該是x的約數。我們來證明一下:(2*x)%a=0
,設2*x=n*a
,因為a是奇數,2*x
是偶數,則必有n是偶數。又因為 x=(n/2)*a
,所以 x%a=0
,即a是x的約數。因為a也是y的約數,所以a是x和y的公約數,有 gcd(2*x,y) <= gcd(x,y)
。因為gcd(x,y)
明顯是2*x
和y的公約數,又有gcd(x,y) <= gcd(2*x,y)
,所以 gcd(2*x,y) = gcd(x,y)
。至此,我們得出了一奇一偶時化小的方法。
兩個奇數:
設有兩個奇數x和y,似乎x和y直接向小轉化沒有什么太好的辦法,我們可以繞個道,把x和y向偶數靠攏去化小。不妨設x>y,我們注意到x+y和x-y是兩個偶數,則有 gcd( x+y,x-y ) = 2 * gcd( (x+y)/2,(x-y)/2 ),那么 gcd( x,y ) 與 gcd( x+y,x-y ) 以及 gcd( (x+y)/2,(x-y)/2 ) 之間是不是有某種聯系呢?為了方便我設 m=(x+y)/2 ,n=(x-y)/2 ,容易發現 m+n=x ,m-n=y 。設 a = gcd( m,n ),則 m%a=0,n%a=0 ,所以 (m+n)%a=0,(m-n)%a=0 ,即 x%a=0 ,y%a=0 ,所以a是x和y的公約數,有 gcd( m,n )<= gcd(x,y)。再設 b = gcd( x,y )肯定為奇數,則 x%b=0,y%b=0 ,所以 (x+y)%b=0 ,(x-y)%b=0 ,又因為x+y和x-y都是偶數,跟前面一奇一偶時證明a是x的約數的方法相同,有 ((x+y)/2)%b=0,((x-y)/2)%b=0 ,即 m%b=0 ,n%b=0 ,所以b是m和n的公約數,有 gcd( x,y ) <= gcd( m,n )。所以 gcd( x,y ) = gcd( m,n ) = gcd( (x+y)/2,(x-y)/2 )。
整理一下,對兩個正整數 x>y :
1.均為偶數 gcd(x,y) = 2gcd(x/2,y/2);
2.均為奇數 gcd(x,y) = gcd((x+y)/2,(x-y)/2);
2.x奇y偶 gcd(x,y) = gcd(x,y/2);
3.x偶y奇 gcd(x,y) = gcd(x/2,y) 或 gcd(x,y)=gcd(y,x/2);
Stein算法不采用除法和取模運算,而是采用整數的移位和最普通的加減法。這樣,在計算超過64位的整數時,算法執行效率非常高。Stein算法的執行過程如下:
(1)對於已知兩自然數a、b,a和b誰大誰小沒有要求;
(2)首先判斷a或b的值,如果a=0,b就是最大公約數;如果b=0,a就是最大公約數,從而可以直接完成計算操作。如果a和b均不為0,則執行下一步。
(3)判斷a,b的大小,m保存大的數,n保存小的數,比如a>b,完成如下賦值:m=a、n=b。
(4)判斷m和n是否為偶數,若都是偶數,記錄下公約數為2,同時使m=m/2,n=n/2,。
(5)若m是偶數,n是奇數,使m=m/2,遞歸調用。
(6)若n是偶數,m是奇數,使n=n/2,遞歸調用。
(7)若m和n都是奇數,使m=(m+n)/2,n=(m-n)/2,遞歸調用。
以上述算法的執行過程中,反復用到除2和乘2的操作。其實乘2只需將二進制整數左移一位,而除2只需要將二進制整數右移一位即可,這樣程序的執行效率更高。
2.3 更相減損術算法,又稱輾轉相減法,尼考曼徹斯法
轉相減法是一種簡便的求出兩數最大公約數的方法。
輾轉相減法(求最大公約數),即尼考曼徹斯法,其特色是做一系列減法,從而求得最大公約數。例如 :兩個自然數35和14,用大數減去小數,(35,14)->(21,14)->(7,14),此時,7小於14,要做一次交換,把14作為被減數,即(14,7)->(7,7),再做一次相減,結果為0,這樣也就求出了最大公約數7
更相減損法最早是出自《九章算術》的一種求最大公約數的算法,它原本是為約分而設計的,但它適用於任何需要求最大公約數的場合。又名“更相減損術”,輾轉相減法,等值算法,尼考曼徹斯法,被廣泛應用在數學和計算機上。
它的基本原理就是:大數減小數,直到兩數相等時,即為最大公約數。
輾轉相減法的執行過程如下:
(1)對於已知兩自然數a、b;
(2)如果a>b,以較大的數減較小的數,接着把所得的差a-b賦值給a,與較小的數b繼續比較繼續相減;
(3)如果a<b,以較大的數減較小的數,接着把所得的差b-a賦值給b,與較小的數a繼續比較繼續相減;
(4)直到a=b時,最大公約數就出來了
2.4 短除法
短除法是求最大公約數的一種方法,它也可用來求最小公倍數。求幾個數最大公約數的方法,開始時用觀察比較的方法,即:先把每個數的因數找出來,然后再找出公因數,最后在公因數中找出最大公約數。后來,使用分解質因數法來分別分解兩個數的因數,再進行運算。之后又演變為短除法。短除法運算方法是先用一個除數除以能被它除盡的一個質數,以此類推,除到商是質數為止。
短除法求最大約數,先用這幾個數的公約數連續去除,一直除到所有的商互質為止,然后把所有的除數連乘起來,所得的積就是這幾個數的最大公約數。例如,求24、60的最大公約數。(24、60)=2×2×3=12
無論是短除法,還是分解質因數法,在質因數較大時,都會覺得困難。這時就需要用新的方法。
它的基本原理就是:用這幾個數的公約數連續去除,一直除到所有的商互質為止,然后把所有的除數連乘起來,所得的積就是這幾個數的最大公約數
短除法的執行過程如下:
(1)對於已知兩自然數a、b;
(2)設定j=1,i=2為初始值,a,b同時對i取模運算,如果取模都為0,j=j*i;直到數據
2.5 質因數分解法
分解質因數只針對合數。(分解質因數也稱分解素因數)求一個數分解質因數,要從最小的質數除起,一直除到結果為質數為止。分解質因數的算式叫短除法,和除法的性質相似,還可以用來求多個數的公因式。
把每個數分別分解質因數,再把各數中的全部公有質因數提取出來連乘,所得的積就是這幾個數的最大公約數。例如:求24和60的最大公約數,先分解質因數,得24=2×2×2×3,60=2×2×3×5,24與60的全部公有的質因數是2、2、3,它們的積是2×2×3=12,所以(24、60)=12。
短除法和質因數分解法原理類似,就不在贅述
三、求最大公約數編程實現
package com.joshua317;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int a, b;
/**
System.out.println("最大公因數求解");
System.out.println("請輸入第1個整型數據");
Scanner scanner = new Scanner(System.in);
a = scanner.nextInt();
System.out.println("請輸入第2個整型數據");
b = scanner.nextInt();
*/
a = 24;
b = 60;
int gcd = gcd5(24,60);
System.out.println("最大公約數為: " + gcd);
}
//歐幾里德算法-迭代實現
public static int gcd(int a, int b)
{
int r;
r = a % b;
while (r != 0) {
a = b;
b = r;
r = a % b;
}
return b;
}
//歐幾里德算法-遞歸實現
public static int gcd1(int a, int b)
{
int r = a % b;
if (r == 0) {
return b;
}
a = b;
b = r;
return gcd1(a, b);
}
//Stein算法-遞歸實現
public static int gcd2(int a, int b)
{
int m, n;
if (a >= b) {
m = a;
n = b;
} else {
m = b;
n = a;
}
if (n == 0) {
return m;
}
if (m%2 == 0 && n%2==0) {
return 2 * gcd2(m/2, n/2);
}
if (m%2 == 0) {
return gcd2(m/2, n);
}
if (n%2 == 0) {
return gcd2(m, n/2);
}
return gcd2((m+n)/2, (m-n)/2);
}
//輾轉相減法 迭代實現
public static int gcd3(int a, int b)
{
while (a == b) {
if (a - b > 0) {
a -= b;
} else {
b -= a;
}
}
return a;
}
//輾轉相減法 遞歸實現
public static int gcd4(int a, int b)
{
if (a == b) {
return a;
} else if (a > b) {
return gcd4(a-b, b);
} else {
return gcd4(b-a, a);
}
}
public static int gcd5(int a, int b)
{
int i, j = 1;
for (i = 2; i <= a && i <= b; i++)
{
while (a % i == 0 && b % i == 0)
{
j = j * i;
a = a / i; b = b / i;
}
}
return j;
}
}
本文為joshua317原創文章,轉載請注明:轉載自joshua317博客 https://www.joshua317.com/article/140