Reputation: 345
I want to calculate if a number is a perfect number (sum(proper divisors) == number). So all I have to do is get the proper divisors, add them up and look if it's the number. For this I use a for-loop:
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;}
This loop is too slow. What I have done already as you can see is break out of the loop if the sum is already bigger than the number, I start from higher numbers (so if the sum is greater, it gets there faster), and since 1 is always a proper divisor, I don't need to loop over it.
Now I'm kind of out of ideas and would really appreciate some tips on how to improve this loop further (or even a completely different approach?)
Upvotes: 1
Views: 508
Reputation: 518
You can implement some simple optimizations:
sum += i + number/i
. Natural order instead of reverse would be generally faster because it would instantly find small divisors which will increase the sum significantly.If you are solving this problem for very large numbers you cannot do it really fast because it's an instance of Integer factorization problem which is known to be very hard to solve efficiently (i.e there are no known polynomial algorithm). So there are no way to make this loop much faster (asymptotically) than proposed implementation.
But on the other hand integer factorization is an old and very important problem so there are libraries which solve it incredebly fast with advanced heuristics and assembler-level optimizations.
Upvotes: 0
Reputation:
There are some ways to optimize that.
thread
s. (Depends)Upvotes: 0
Reputation: 20268
If you really want to schrink the time of this loop and test big numbers, first you can try Miller-Rabin test to eliminate prime numbers. Then use Fermat factorisation method to find number's divisors.
If You test small numbers, You should iterate from 1 and test numbers only until the square root of the number (reference).
Upvotes: 1
Reputation: 17714
You can get a very large improvement in the following way:
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;}
As you can see, this loop starts at the square root of the input, instead of half the input. This gives an O(sqrt(N)) improvement in the worst case. For any pair of divisors of a number, one must lie above the square root and one most lie below. Another important thing to be aware of is that integer division/modulus is very expensive, but when they are computed, both are computed simultaneously. That means that once you have computed number % i
, number / i
is basically free. Hence, the cost of each iteration in my code is basically identical to the cost per iteration in yours, but there are many fewer iterations.
You may also want to consider counting up instead of down if you do this, if you goal is to exit early then generally speaking you will do better to start with the small numbers because the sum of more extremal divisors (one very high, one very low) is larger. Also with smaller numbers there will be a higher density of divisors.
Note that my code is not exactly correct, there are some edge cases to consider, perfect squares are one example.
Upvotes: 4