多个数的最大公约数、最小公倍数算法

首先,笔者假设大家已经会辗转相除法和更相减损术。

两个数的最大公约数(GCD)、最小公倍数(LCM)用上面两个算法实现非常简单。那么如果是同时求多个数的呢?

首先约定两个数的最大公约数的函数为gcd,最小公倍数的函数为lcm

多个数的最大公约数算法

  1. 自然而然会想到用逐轮用2个数计算,将上一步的最大公约数与下一个数继续计算,直到计算完所有数。
function multi_gcd_step() {
   // 拿到参数,并都转成正数
   let arr = Array.from(arguments).map(a => a > 0 ? a : -a);
   // 参数非法过滤
   if (arr.length < 2) {
       throw new Error('参数个数不能少于2');
   }
   if (arr.some(a => !a)) {
       throw new Error('参数不能包含0');
   }
   for (let i = 0; i < arr.length - 1; i++) {
       let c = gcd(arr[i], arr[i + 1]);		// gcd为两个数的最大公约数算法
       if (c === 1) {
           // 如果最大公约数是1,则提前结束
           return 1;
       }
       arr[i + 1] = c;
   }
   return arr[arr.length - 1];	// 返回最后一项
}
  1. 上面的算法比较麻烦,因为不断调用内部有多次循环的 gcd 算法。其实,根据辗转相除法,我们可以这样做:
    1. 对这一组数进行排序(从大到小)
    2. 对每两个相邻的两个数进行如下操作:设相邻的两个数为A和B(A在前,因为已经排序,所以A > B),如果A = n * B(n为整数),也就是A能够被B整除,那么就令A = B;如果A不能被B整除则令A = A % B。
    3. 重复上面两步,直到数组中每个数都相等,则最大公约数就为这个数。
function multi_gcd_zzxcf() {
    // 拿到参数
    let arr = Array.from(arguments).map(a => a > 0 ? a : -a);
    // TODO: 参数非法过滤
    while (!arr.every(a => a === arr[0])) {
        // 从大到小排序
        arr.sort((a, b) => b - a);
        // 假设前一个数是A,后一个是B
        // 如果A能够被B整除,那么就令A = B;如果A不能被B整除则令A = A % B
        for (let i = 0; i < arr.length - 1; i++) {
            let c = arr[i] % arr[i + 1];
            arr[i] = c || arr[i + 1];
        }
    }
    return arr[0];
}

多个数的最小公倍数算法

我们知道,两个数a, b的最小公倍数算法,是 ab / gcd(a, b),即两者乘积除以它们的最大公约数。那么多个数的也可以这么计算吗?答案是否定的,比如(9, 10, 5),最大公约数为1,最小公倍数 90 ≠ 9 × 10 × 5 ÷ 1。所以不能基于上面的多个数的最大公约数算法。

  1. 不断计算两个数的最小公倍数,将上一步的最小公倍数和下一个计算,直到遍历完,最终的结果即为所有数的最小公倍数
function multi_lcm_step() {
	let arr = Array.from(arguments).map(a => a > 0 ? a : -a);
	// TODO: 参数非法过滤
    for (let i = 0; i < arr.length - 1; i++) {
        arr[i + 1] = lcm(arr[i], arr[i + 1]);	// lcm 为两个数的最小公倍数算法
    }
    return arr[arr.length - 1];
}
    1. 计算m=a1*a2*..*an
    2. 把a1,a2,…,an中的所有项ai用 m/ai 代换
    3. 找到a1,a2,…,an中的最小非零项aj,若有多个最小非零项则任取一个
    4. aj以外的所有其他非0项ak用ak % aj代替;若aj以外,其他都变成了0,则转到(6)
    5. 转到(3)
    6. 最小公倍数为m/aj
function multi_lcm_zzxcf() {
    let arr = Array.from(arguments).map(a => a > 0 ? a : -a);
    // TODO: 参数非法过滤
    let m = arr.reduce((a, b) => a * b, 1);
    arr = arr.map(a => m / a);
    let aj = m;
    do {
        // 找非0最小项
        let ajIndex = -1;
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] < aj) {
                aj = arr[i];
                ajIndex = i;
            }
        }
        // 除了非零最小项,其他都求余
        for (let i = 0; i < arr.length; i++) {
            if (i !== ajIndex) {
                arr[i] = arr[i] % aj;
            }
        }
        // 过滤掉0,减少和方便计算
        arr = arr.filter(a => !!a);
    } while (arr.length);
    return m / aj;
}

参考连接:https://www.cnblogs.com/leiyuxiang/articles/3494977.html

你可能感兴趣的:(算法)