【问题标题】:How to return all the divisors from a big integer?如何从一个大整数中返回所有除数?
【发布时间】:2022-10-06 15:37:12
【问题描述】:

我了解如何从给定整数返回所有除数的概念。然而,当它到达大整数时,没有任何回报:

function divisors(n,res=[]) {
  for (let i = 1; i <= n; i++) !(n%i) && res.push(i);
  return res;
}

console.log(divisors(4)); // [ 1, 2, 4 ]
console.log(divisors(9)); // [ 1, 3, 9 ]
console.log(divisors(12)); // [ 1, 2, 3, 4, 6, 12 ]
console.log(divisors(975179493674)); // ?????
console.log(divisors(27550356289)); // ?????

下一个合乎逻辑的步骤是通过在 for 循环中取给定整数 n 的平方根来最小化迭代量。这有效并部分返回了一些除数,但它没有返回每个整数的所有除数。

function divisors(n,res=[]) {
  for (let i = 1; i <= Math.floor(Math.sqrt(n)); i++) {
    !(n%i) && res.push(i)
  }
  return res
}

console.log(divisors(4)); // [ 1, 2 ]
console.log(divisors(9)); // [ 1, 3 ]
console.log(divisors(12)); // [ 1, 2, 3 ]
console.log(divisors(975179493674)); // [ 1, 2, 97, 194 ]
console.log(divisors(27550356289)); // [ 1, 165983 ]

我就是无法完全理解它。任何帮助或指示将不胜感激。

更新:

仅使用 1 个循环仍然可以解决此问题。它缺少几行控制流,如下所示:

function divisors(n,res=[]) {
  for (let i = 1; i <= Math.sqrt(n); i++) {
    if (!(n%i)) {
      i*i < n && res.push(i);
      res.push(n/i);
    }
  }
  return res;
}

console.log(divisors(4)); // [ 1, 2 ]
console.log(divisors(9)); // [ 1, 3 ]
console.log(divisors(12)); // [ 1, 2, 3 ]
console.log(divisors(975179493674)); // [ 1, 2, 97, 194, 5026698421, 10053396842, 487589746837, 975179493674 ]
console.log(divisors(27550356289)); // [ 1, 165983, 27550356289 ]

标签: javascript algorithm


【解决方案1】:

找到所有小于或等于输入平方根的除数后,您可以将输入除以这些数字以获得其余除数:

function divisors(n,res=[]) {
  for (let i = 1; i <= Math.floor(Math.sqrt(n)); i++) {
    if (n%i == 0) { res.push(i) }
  }
  let len = res.length;
  // if n is a square, don't include the square root twice
  if (res[len-1] * res[len-1] == n) len--;
  for (i = len - 1; i >= 0; i--) { res.push(n / res[i]) }
  return res
}

console.log(divisors(4)); // [ 1, 2, 4 ]
console.log(divisors(9)); // [ 1, 3, 9 ]
console.log(divisors(12)); // [ 1, 2, 3, 4, 6, 12 ]
console.log(divisors(975179493674)); // [ 1, 2, 97, 194, 5026698421, 10053396842, 487589746837, 975179493674 ]
console.log(divisors(27550356289)); // [ 1, 165983, 27550356289 ]

【讨论】:

  • 谢谢回复。您的方法非常简洁,因为您使用正向和反向循环来返回相应的结果。我正在考虑,而不是定义“len”和附加控制流,如何重构为具有嵌入式控制流的 2 个循环,如下所示:function divisors(n,res=[]) { for (let i = 1; i*i &lt; n; i++) !(n%i) &amp;&amp; res.push(i); for (let i = Math.floor(Math.sqrt(n)); i &gt;= 1; i--) !(n%i) &amp;&amp; res.push(n/i); return res; }
  • @noirsociety 你提出的问题是你的第二个循环需要再次迭代所有可能的除数,所以它会比我的慢得多,这只要迭代找到的实际除数。另请注意,对于n=4,它将返回[1,2,2,4](这是我一点点控制流的重点)
  • 然后,它真的让我思考......为什么不在一个“for循环”中编写一个正向和反向循环。所以出现了:function divisors(n,left=[],right=[]) { const t = Math.floor(Math.sqrt(n));让 c = 0;让 d = t; for (let i = 1; i <= t; i++) { i <= t && c++ && d-- !(n%c) && i*i < n && left.push(c) !(n%d) && right.push(n/d); } 返回 [...left,...right]; }
  • @noirsociety 是的,这不是一个坏主意,如果有点难以阅读:)
  • 我借用了你的很多想法和逻辑。因为我对编程有点陌生,还没有学习大符号,但我认为一个循环比 2 好,对吧?尽管如此,我不知道这个版本的效率如何,所以欢迎任何建设性的批评来帮助我改进:)
【解决方案2】:

在这里区分几个相似但不同的问题是有意义的:

(1) 要检查一个数 N 是否为质数,您可以在达到其平方根时停止搜索可能的除数。那是因为如果N == x * yx &gt; sqrt(N),那么y &lt; sqrt(N),所以你会在找到x 之前找到y
以具体数字为例:要检查 11 是否为素数,您可以在检查 11 % 2 != 011 % 3 != 0 后停止搜索(因为 sqrt(11) &lt; 4)。如果 4, 5, 6, ... 中的任何一个是 11 的除数,那么就会有一个相应的除数 11/4 或 11/5 或 11/6 等,所有这些都小于 sqrt(11),所以你以前会发现它们的。

(2) 寻找所有主要因素一个数字 N,你不能只需在 sqrt(N) 处停止搜索。与情况(1)相反:如果您只想测试 10 是否为素数,则可以在检查 3 == floor(sqrt(10)) 后停止搜索除数(此时您会找到 2,证明10 不是素数),而如果你的任务是找到全部主要因素,您还需要以某种方式找到 5,并且 5 > sqrt(10)。

实现这一目标的一种方法是将 N 除以您找到的每个因素,因此您将得到类似的结果:

function primeFactors(n,res=[]) {
  for (let i = 2; i <= Math.floor(Math.sqrt(n)); ) {
    let candidate = Math.floor(n / i);
    if (candidate * i === n) {
      res.push(i);
      n = candidate;
    } else {
      i++;
    }
  }
  res.push(n);
  return res;
}

请注意,这里使用candidate * i === n 而不是n % i === 0,因为乘法比除法快得多。由于我们已经有了n / i 除法(并且在这种方法中无法避免这种情况),我们至少可以用该乘法替换第二个n % i 除法。
同样,如果将循环的条件i &lt;= Math.floor(Math.sqrt(n)) 替换为i*i &lt;= n,则可以进一步提高性能。或者更好的是,重用我们已经完成的工作:if (candidate &lt; i) break;

(3) 寻找所有除数(主要因素和复合除数),您可以从几个方向解决问题:
最简单的可能是按照@Nick 的回答所建议的去做:尝试从 1 到 sqrt(N) 的所有候选人 i,每当找到一个时,将 in / i 添加到列表中。
作为对此的一个小改进,您可以从i = 2 开始,并始终将1n 添加到列表中而不进行检查(因为每一个整数可被 1 和自身整除)。

另一种可能更快但实施起来也更复杂的替代方法是首先找到主要因素(参见(2)),然后构建所有除数的集合作为主要因素的幂集。例如,如果您发现素因数是[2, 3, 5],那么除数集就是[1, 2, 3, 5, 2*3, 2*5, 3*5, 2*3*5]。 (请注意,当某些主要因素不止一次出现时,这将需要一些重复数据删除。)

如果性能真的很重要,那么您可以做的更多。例如,您可以缓存找到的素数,并在随后的调用中只检查那些可能的除数。
在这个方向上一个非常简单的步骤是特殊情况 i=2,然后只检查奇数候选(3、5、7、...)。这个简单的技巧将节省大约一半的工作!
甚至可以完全摆脱昂贵的除法,代价是花费更多的内存来跟踪需要检查的每个素数的下一个倍数......但这离你原来的问题有点远!在过于关注优化之前,我想指出,对于单次调用,即使对于像 975179493674 这样的输入,任何这样的调整都不值得付出努力:您将节省几毫秒的执行时间,但实施起来至少需要几分钟。但是,如果这恰好是应用程序的性能关键核心部分,那么它为投资实施工作以节省执行时间提供了相当大的空间。

【讨论】:

    猜你喜欢
    • 2016-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多