尝试优化和理解打印数字除数的递归函数的运行时

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

本文关键字:递归函数 运行时 数字 打印 优化      更新时间:2023-10-16

下面是一个递归函数,它打印一个数字的除数(它必须是递归的(。

如果没有注释掉的部分,程序最坏的情况是素数,while 循环必须一直运行到 n,因此运行时将是:O(n(。

对于注释部分,素数现在在 O(sqrt(n(( 上运行,但在不是素数的数字上它会变慢,但 for 循环在任何情况下都应该使计数到除数的速度更快。谁能解释为什么运行时更慢?

如果有人想自己检查,我已经包括了时钟部分,这里有一些大的素数: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);
}

问题分析:

问题很明显:您注释的for循环将在递归调用中执行 1.641.428.459 次,每次在long long上执行模块操作。 这需要一些时间! 这解释了它几乎是 3 倍长(在我的 PC 上大约 40 秒(

首先,这个 for 循环根本没有优化! 以下:

  for (i = 1; i <= sn; i++) 
  ...
  if (trigger == 0) divisor = n; 

可以简化,因为trigger仅用于知道它是否为 0:

  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; 

通过此更改,您的其他代码将获得可接受的性能。 但是,此代码仍然不会加速任何内容。 总体而言,它仍然比没有慢 100 毫秒,大约需要 15.5 秒才能获得结果。

B.Kernighan曾经说过">不要嘻嘻代码,找到更好的算法":看看你的回避代码和for循环,我们可以看到,每次递归都盯着1进行同样的i计算。 但是,如果 2 或 3 或 5 不是第一次除数,那也不会是 secont 时间! 因此,我们可以优化递归:

优化递归

使用以下代码,您可以获得 1.3 秒的结果,而 15.5 秒的速度快 10 倍以上:

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 !  
}