我想计算一个数字是否是一个完美的数字(总和(适当的除数)==数字)。因此,我所要做的就是得到适当的除数,将它们加起来并查看它是否为数字。为此我使用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总是一个适当的除数,我不需要循环它。
现在我有点想法,并且非常感谢有关如何进一步改进这种循环的一些提示(甚至是完全不同的方法?)
答案 0 :(得分:4)
您可以通过以下方式获得非常大的改进:
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 % i
,number / i
基本上是免费的。因此,我的代码中每次迭代的成本基本上与您的迭代成本相同,但迭代次数要少得多。
如果你这样做,你可能还想考虑计数而不是减少,如果你的目标是提前退出,那么一般来说你会更好地从小数字开始,因为更多极值除数的总和(一个非常高) ,一个非常低)更大。数量越小,除数密度越高。
请注意,我的代码并不完全正确,需要考虑一些边缘情况,完美的正方形就是一个例子。
答案 1 :(得分:1)
如果你真的想缩短这个循环的时间并测试大数字,首先你可以尝试Miller-Rabin test来消除素数。然后使用Fermat factorisation method查找数字的除数。
如果您测试小数字,您应该从1开始迭代并仅测试数字,直到数字的平方根(reference)。
答案 2 :(得分:0)
您可以实施一些简单的优化:
sum += i + number/i
。自然顺序而不是反向通常会更快,因为它会立即找到小的除数,这会显着增加总和。如果你要为非常大的数字解决这个问题,你就不能很快地做到这一点,因为它是Integer factorization问题的一个例子,已知这个问题很难有效地解决(即,没有人知道多项式算法)。因此,没有办法使这个循环比提议的实现更快(渐近)。
但另一方面,整数分解是一个古老且非常重要的问题,因此有些库可以通过高级启发式和汇编级优化来快速解决它。
答案 3 :(得分:0)
有一些方法可以优化它。
thread
s的神奇力量。 (视乎)