使循环更快

Making a loop faster

本文关键字:循环      更新时间:2023-10-16

我想计算一个数字是否为完全数(和(固有除数)==数)。所以我要做的就是找到合适的除数,把它们加起来,看看是不是那个数。为此,我使用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总是一个合适的除数,我不需要循环它。

现在我有点无计可施了,希望你能给我一些建议,告诉我如何进一步改进这个循环(或者甚至是一种完全不同的方法?)

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

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(√N)的改进。对于一个数的任何一对除数,一个必须在平方根的上面,一个最在根号的下面。另一件需要注意的重要事情是整数除法/模数是非常昂贵的,但是在计算它们时,两者是同时计算的。这意味着一旦你计算出number % i, number / i基本上是自由的。因此,我的代码中每次迭代的成本基本上与您的代码中每次迭代的成本相同,但是迭代次数要少得多。

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

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

如果您真的想缩短这个循环的时间并测试大数,首先您可以尝试Miller-Rabin测试来消除素数。然后用费马分解法求出数的因数。

如果你测试小的数字,你应该从1迭代,只测试数字的平方根(引用)。

有一些方法可以优化。

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

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

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

如果你要解决这个问题对于非常大的数字,你不能做得很快,因为它是一个整数分解问题的实例,这是一个很难有效解决的问题(即没有已知的多项式算法)。因此,没有办法使这个循环比建议的实现更快(渐近)。

但另一方面,整数分解是一个古老而非常重要的问题,因此有一些库可以通过高级启发式和汇编级优化以令人难以置信的速度解决它。