问题
The following is a recursive function that prints the divisors of a number (it has to be recursive).
Without the commented out part, the program's worst case scenario are primes, the while loop will have to run all the way up to n, so the runtime would be: O(n).
With the commented part, primes now run on O(sqrt(n)) but it becomes slower on numbers that aren't primes, but that for loop should make counting up to a divisor faster in any case. Can anyone explain why the runtime is slower?
I've included the clock part if anyone want to check for themselves, here's some large prime: 1073676287.
#include<iostream>
#include <math.h>
#include <ctime>
using namespace std;
void f(long long number);
void main()
{
clock_t begin = clock();
f(1844674407370955161);
clock_t end = clock();
cout << endl << double(end - begin) / CLOCKS_PER_SEC << endl;
}
void f(long long n)
{
long long divisor = 2;
long long sn = sqrt(n), i;
int trigger = 0;
if (n == 1) return;
//else if (n != 1)
//{
// for (i = 2; i <= sn; i++)
// {
// if (n%i == 0)
// trigger++;
// }
// if (trigger == 0)divisor = n;
//}
while ((n % divisor) && (n > divisor))
divisor++;
cout << divisor << " ";
f(n / divisor);
}
回答1:
Analysis of the problem:
The problem is obvious: Your commented for
loop will be executed accross the recursive calls 1.641.428.459 times, performing each time a module operation on a long long
. This needs some time ! This explain how it's almost 3 times as long (around 40s on my PC)
First this for loop is is not optimized at all ! The following:
for (i = 1; i <= sn; i++)
...
if (trigger == 0) divisor = n;
can be simplified, because trigger
is used only to know if it's 0 or not:
for (i = 1; !trigger && i <= sn; i++) { // if trigger is set, no use to continue !!
if (n%i == 0)
trigger++;
}
if (trigger == 0) divisor = n;
With this change, your additional code gains acceptable performance. However this code still doesn't accelarate anything. Overall it's still 100 ms slower than without, and around 15.5 seconds to get the results.
B.Kernighan has once said "Don't diddle code, find better algorithms": looking at your recusive code and the for
loop, we can see that the same calculations with i
, staring at 1 are done at every recursion. But if 2 or 3 or 5 was not a divisor the first time, it won't be the secont time either ! So we could optimize the recursion:
Optimized recursion
With the following code, you get the results 1.3 seconds compared to 15.5 seconds its more than 10 times faster:
void f(long long number, long long last_divisor=2); // 2 parameters, 1 being by default
void f(long long n, long long last_divisor)
{
long long divisor = last_divisor; // we don't start at 2 anymore
long long sn = sqrt(n), i;
int trigger = 0;
if (n == 1) return;
else if (n != 1)//optimisation in case given number is a prime
{
for (i = last_divisor; !trigger && i <= sn; i++) { // we don't start at 1 anymore
if (n%i == 0)
trigger++;
}
if (trigger == 0)divisor = n;
}
while ((n % divisor) && (n > divisor))
divisor++;
cout << divisor << " ";
f(n / divisor, divisor); // tell recursion where we've stopped !
}
来源:https://stackoverflow.com/questions/27449316/trying-to-optimize-and-understand-runtime-on-a-recursive-function-that-prints-di