问题:
求出两个正整数的最大公约数
解法1:
暴力枚举法,从较小数的一半开始,找到一个合适的整数
function getGreatestCommonDivisorV1(a:number, b:number):number {
let big = a > b ? a :b;
let small = a > b ? b :a;
if(big % small === 0) {
return small
}
for (let i = Math.floor(small/2); i > 1; i--) {
if(small % i ===0 && big % i === 0) {
return i
}
}
return 1
}
解法2:
辗转相除法,又名欧几里得算法
该算法基于一条定律:两个正整数 a 和 b (a > b),它们的最大公约数等于 a 除以 b 的余数 和 b 的最大公约数
function getGreatestCommonDivisorV2(a:number, b:number):number {
let big = a > b ? a : b;
let small = a > b ? b : a;
if(big%small === 0) {
return small
}
return getGreatestCommonDivisorV2(big%small, small)
}
该算法存在问题:大数取模运算性能较差
解法3:
更相减损术,出自《九章算术》
原理:两个正整数 a 和 b (a > b),它们的最大公约数等于 a 减 b 的差值 和 b的最大公约数
function getGreatestCommonDivisorV3(a:number, b:number):number {
if(a === b) {
return a
}
let big = a > b ? a : b;
let small = a > b ? b :a;
return getGreatestCommonDivisorV3(big - small, small)
}
该算法存在问题:递归次数较多
解法4:
此解法基于几个巧妙地方法:
判断一个正整数 a 是否为偶数 a%2 === 0 可以转换为 位运算 (a & 1) === 0
对 正整数 a, a/2 转换为位运算 a >> 1
a*2 转换为位运算 a << 1
求两个正整数的最大公约数 24、60
基于质因数分解法
24 = 2 * 2 * 2 *3
64 = 2 * 2 * 3 * 5
公有质因数为 2 * 2 * 3,则最大公约数为 2 * 2 * 3 = 12
由以上可以得出
设求最大公约数函数 f
当 a, b 都为偶数时 f(a, b) = 2 * f(a, b)
当 a 为偶数,b 为奇数时 f(a, b) = f(a/2, b)
设求最大公约数的方法为 gcd,
当 a,b 都为偶数时, gcd(a, b) = 2 * gcd(a/2, b/2) = 2 * gcd(a >> 1, b >>1)
当 a为偶数,b为奇数时,gcd(a, b) = gcd(a/2, b) = gcd(a >> 1, b)
当 a 为 奇数,b为偶数时,同上转换为 b >> 1
当 a,b 都为 奇数时,使用更相减损法,gcd(a, b) = gcd(a-b, b) 假设 a > b,此时 两奇数相减结果必定为 偶数
function getGreatestCommonDivisorV4(a:number, b:number):number{
if(a === b){
return a
}
let big = a > b ? a : b;
let small = a > b ? b : a;
let isBigEven = (big & 1 )=== 0;
let isSmallEven = (small & 1) === 0;
// 两个数都为偶数
if(isBigEven && isSmallEven) {
return getGreatestCommonDivisorV4(big >> 1, small >>> 1) << 1
} else if(isBigEven) {
// 两个数一个为偶数 一个为奇数
return getGreatestCommonDivisorV4(big >> 1, small)
} else if(isSmallEven) {
return getGreatestCommonDivisorV4(big, small >> 1)
} else {
// 两个数都为奇数
return getGreatestCommonDivisorV4(big - small, small)
}
}
摘要总结自: 漫画算法 小灰的算法之旅