転がり相除法(ユークリッドアルゴリズムとも呼ばれる)は何の鬼ですか.
3452 ワード
2つの正の整数の最大公約数を求めることを目的として、転々とした相殺法、別名ユークリッドアルゴリズム(Euclidean algorithm).
このアルゴリズムは、2つの正の整数aおよびb(a>b)に基づいており、それらの最大公約数は、aをbで割った余りcおよびbの間の最大公約数に等しい.例えば、10および25、25を10商2余り5で割った場合、10および25の最大公約数は、10および5の最大公約数に等しい.
更に相減損術は、中国古代の「九章算術」から出ており、最大公約数を求めるアルゴリズムでもある.
彼の原理はもっと簡単です:2つの正の整数aとb(a>b)で、それらの最大公約数はa-bの差cと小数bの最大公約数に等しいです.例えば、10と25、25から10を引く差は15で、10と25の最大公約数は10と15の最大公約数に等しいです.
最後に、上記のすべての解法の時間的複雑さをまとめます.
1.暴力列挙法:時間の複雑さはO(min(a,b))
2.転がり相除算法:時間の複雑さは計算しにくく、O(log(min(a,b))に近似できるが、型取り演算性能は劣る.
3.更相減損術:型取り演算は避けたが、アルゴリズム性能は不安定で、最悪時間複雑度はO(max(a,b))
4.更相減損術とシフトの結合:型取り演算を避けるだけでなく、アルゴリズム性能が安定し、時間複雑度がO(log(max(a,b))))である.
具体的なアルゴリズム:aとbが共に偶数である場合、gcb(a,b)=2 gcb(a/2,b/2)=2 gcb(a>>1,b>>1)
aが偶数,bが奇数である場合,gcb(a,b)=gcb(a/2,b)=gcb(a>>1,b)
aが奇数,bが偶数のとき,gcb(a,b)=gcb(a,b/2)=gcb(a,b>>1)
aとbが共に奇数である場合、より相減損術により1回演算し、gcb(a,b)=gcb(b,a−b)である場合、a−bは必然的に偶数であり、シフト演算を継続することができる.
このアルゴリズムは、2つの正の整数aおよびb(a>b)に基づいており、それらの最大公約数は、aをbで割った余りcおよびbの間の最大公約数に等しい.例えば、10および25、25を10商2余り5で割った場合、10および25の最大公約数は、10および5の最大公約数に等しい.
更に相減損術は、中国古代の「九章算術」から出ており、最大公約数を求めるアルゴリズムでもある.
彼の原理はもっと簡単です:2つの正の整数aとb(a>b)で、それらの最大公約数はa-bの差cと小数bの最大公約数に等しいです.例えば、10と25、25から10を引く差は15で、10と25の最大公約数は10と15の最大公約数に等しいです.
package com.zheting.it.test04;
public class Test {
public static void main(String[] args) {
}
//
public static int getGreatestCommonDivisor(int numberA, int numberB) {
int smallNumber = numberA < numberB ? numberA : numberB;
int bigNumber = numberA >= numberB ? numberA : numberB;
if (bigNumber % smallNumber == 0) {
return smallNumber;
}
int gereatestCommonDiveisor = 1;
for (int i = 2; i < smallNumber / 2; i++) {
if (numberA % i == 0 && numberB % i == 0) {
gereatestCommonDiveisor = i;
}
}
return gereatestCommonDiveisor;
}
// a b(a>b), a b c b 。
public static int getGreatestCommonDivisor2(int numberA, int numberB) {
int result = 1;
if (numberA > numberB) {
result = gcd2(numberA, numberB);
} else {
result = gcd2(numberB, numberA);
}
return result;
}
private static int gcd2(int a, int b) {
if (a % b == 0) {
return b;
} else {
return gcd2(b, a % b);
}
}
// a b(a>b), a-b c b
public static int getGreatestCommonDivisor3(int numberA, int numberB) {
if(numberA == numberB){
return numberA;
}
if (numberA < numberB) {
return getGreatestCommonDivisor3(numberB - numberA, numberA);
} else {
return getGreatestCommonDivisor3(numberA - numberB, numberB);
}
}
//
public static int getGreatestCommonDivisor4(int numberA, int numberB) {
if(numberA == numberB){
return numberA;
}
if (numberA < numberB) {
return getGreatestCommonDivisor4(numberB, numberA);
} else {
boolean bA = (numberA & 1) == 1;
boolean bB = (numberB & 1) == 1;
if(!bA && !bB){
return getGreatestCommonDivisor4(numberA>>1, numberB>>1) <<1;
}else if(!bA && bB){
return getGreatestCommonDivisor4(numberA>>1, numberB) ;
}else if(bA && !bB){
return getGreatestCommonDivisor4(numberA, numberB>>1);
}else {
return getGreatestCommonDivisor4(numberA, numberA - numberB);
}
}
}
}
最後に、上記のすべての解法の時間的複雑さをまとめます.
1.暴力列挙法:時間の複雑さはO(min(a,b))
2.転がり相除算法:時間の複雑さは計算しにくく、O(log(min(a,b))に近似できるが、型取り演算性能は劣る.
3.更相減損術:型取り演算は避けたが、アルゴリズム性能は不安定で、最悪時間複雑度はO(max(a,b))
4.更相減損術とシフトの結合:型取り演算を避けるだけでなく、アルゴリズム性能が安定し、時間複雑度がO(log(max(a,b))))である.
具体的なアルゴリズム:aとbが共に偶数である場合、gcb(a,b)=2 gcb(a/2,b/2)=2 gcb(a>>1,b>>1)
aが偶数,bが奇数である場合,gcb(a,b)=gcb(a/2,b)=gcb(a>>1,b)
aが奇数,bが偶数のとき,gcb(a,b)=gcb(a,b/2)=gcb(a,b>>1)
aとbが共に奇数である場合、より相減損術により1回演算し、gcb(a,b)=gcb(b,a−b)である場合、a−bは必然的に偶数であり、シフト演算を継続することができる.