Trying to optimize and understand runtime on a recursive function that prints divisors of a number

那年仲夏 提交于 2020-02-25 08:32:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!