【问题标题】:Making a loop faster使循环更快
【发布时间】:2016-06-26 00:45:10
【问题描述】:

我想计算一个数字是否是一个完美的数字(sum(proper divisors)== number)。所以我所要做的就是得到正确的除数,把它们加起来,看看它是否是数字。为此,我使用了一个 for 循环:

cin >> number;
sum = 1;
for (int i = number/2; i > 1; --i) {
  if (number % i == 0) {
    sum = sum + i;
  }
  if (sum > number) {break;}
}
if (sum == number) {cout << "perfect!" << endl;}

这个循环太慢了。如您所见,我已经完成的是,如果总和已经大于数字,则跳出循环,我从更大的数字开始(因此,如果总和更大,它会更快到达那里),并且由于 1 始终是适当的除数,我不需要循环它。

现在我有点没有想法了,非常感谢一些关于如何进一步改进这个循环的提示(或者甚至是完全不同的方法?)

【问题讨论】:

  • number % i == 0number % (number/i) == 0也是
  • 一个可能的技巧 - 不是从number/2 开始,而是从sqrt(number) 开始。注意,如果i是一个除数,那么number/i也是一个除数,所以你只需要访问两者中较小的一个。
  • @nwp 不是这样,或者至少不是微不足道的。 28=1+2+4+7+14 是一个完美数,但 4 不是素数。
  • 更简单的技巧是简单地硬编码所有完美数字。只有 five or six 适合 32 位 int,只有几个适合 64 位。
  • @Nils_S 就像我说的,你只需要总结divisornumber/divisor。所以当你访问 2 时,你添加了 2 和 3。

标签: c++ loops optimization


【解决方案1】:

您可以通过以下方式获得非常大的改进:

cin >> number;
sum = 1;
for (int i = sqrt(number); i > 1; --i) {
  if (number % i == 0) {
    sum += i + (number / i);
  }
  if (sum > number) {break;}
}
if (sum == number) {cout << "perfect!" << endl;}

如您所见,此循环从输入的平方根开始,而不是输入的一半。这在最坏的情况下提供了 O(sqrt(N)) 的改进。对于一个数的任何一对除数,一个必须位于平方根之上,一个位于平方根之下。另一个需要注意的重要事情是整数除法/模数非常昂贵,但是在计算它们时,两者都是同时计算的。这意味着一旦你计算了number % inumber / i 基本上是免费的。因此,我的代码中每次迭代的成本与您的代码中的每次迭代成本基本相同,但迭代次数要少得多。

如果您这样做,您可能还需要考虑向上计数而不是向下计数,如果您的目标是提前退出,那么一般来说,您最好从小数字开始,因为更多极值除数的总和(一个非常高, 一个非常低) 较大。此外,数字越小,除数的密度也会越高。

请注意,我的代码并不完全正确,有一些边缘情况需要考虑,完美的正方形就是一个例子。

【讨论】:

  • 我认为改进是 O(N) -O(SQRT(N))。计数是一个非常好的点。 +1
  • @EvilTeach 旧算法是 N,新算法是 sqrtN,所以它是 sqrtN 的一个因子更好。我是除而不是减。
  • 完美的正方形怎么样,在这种情况下 sqrt(number) 不应该被计算两次吗?
  • 这就是我认为他写最后一句话的原因;)。我知道了,谢谢!
【解决方案2】:

如果你真的想缩短这个循环的时间并测试大数,首先你可以尝试Miller-Rabin test 来消除素数。然后用Fermat factorisation method求数的除数。

如果您测试小数字,您应该从 1 开始迭代并仅测试数字直到数字的平方根 (reference)。

【讨论】:

    【解决方案3】:

    有一些方法可以优化它。

    1. 循环 sqrt(N) 次,而不是 N/2 次。 (O(sqrt(N))
    2. 使用预生成的数组。 (O(0)?)
    3. 使用threads的神奇力量。 (视情况而定)
    4. 使用筛子去除素数。 (O(N) ~ O(N^2))

    【讨论】:

      【解决方案4】:

      你可以实现一些简单的优化:

      1. 只检查素数。您可以使用简单的sieve 快速且高效地找到它们。您也可以找到它们一次并保存在文件中。 UPD:小心,在这样的实现中很容易引入错误。一种可能的正确方法是找到所有除数,将它们保存在数组中,然后遍历所有可能的组合以计算实际总和。它可以递归完成。
      2. 从 2 开始,一直到数字的平方根,如果找到除数,请执行 sum += i + number/i。自然顺序而不是逆序通常会更快,因为它会立即找到会显着增加总和的小除数。

      如果您要解决非常大的数字的问题,您将无法快速解决,因为它是Integer factorization 问题的一个实例,已知很难有效地解决(即没有已知的多项式算法)。所以没有办法让这个循环比提议的实现更快(渐近)。

      但另一方面,整数因式分解是一个古老且非常重要的问题,因此有一些库可以通过先进的启发式和汇编程序级优化快速解决它。

      【讨论】:

      • 28=1+2+4+7+14 如果只检查素数,将找不到。
      • 如果你能正确地在一个循环中检查它们。对不起,我应该是认真的。我会更新我的答案。
      猜你喜欢
      • 2016-05-30
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多