【问题标题】:C speed up recursionC 加速递归
【发布时间】:2021-11-27 01:14:48
【问题描述】:
int test(int n) {
    if(n <= 2)
    {
         return n;
    }
    return test(n-1) + test(n-2) + test(n-3);
}

有什么方法可以在不改变函数声明的情况下加快速度,当n变大时,需要大量时间才能输出。

0.1.2.3.6.11.20

当n = 3时,应该得到输出0+1+2=3

当n = 5时,应该得到输出2+3+6=11

【问题讨论】:

  • 使用记忆化,这样您就不会对已处理的输入进行完全递归。
  • 是否需要使用递归?因为这基本上是一个修改过的斐波那契计算器,而斐波那契实际上是递归的一个可怕问题(它用递归很好地描述了,但它是 O(2**n) 递归实现的,而 O(n) 是迭代实现的;这段代码将是 O(3**n) 递归的,仍然是O(n) 迭代),除非意图是强迫你学习使用记忆来优化递归。
  • “不改变 test(int n)”是什么意思?您只是不想更改函数的声明方式test(int n),还是根本不想更改函数?如果您根本不想更改函数,您如何想象可以在不更改函数源代码的情况下更改函数的行为(包括性能)?更改编译器优化?改变它的叫法?用宏偷偷改变有效源代码?
  • Is there any way to speed it up without changing test(int n) 我唯一的想法就是买更​​快的电脑
  • 肯定有一个O(1) 算法来计算这个,就像斐波那契数的算法一样。但我不知道怎么做。

标签: c recursion


【解决方案1】:

您可能已经知道,递归解决方案缓慢的原因是您重新计算了之前已经计算过的答案。

这就是记忆化起作用的原因,它允许您按照数学定义的方式维护序列的自上而下的算法转换。

完全记忆的缺点是表格可以任意大。如果不需要 O(1) 时间(以 O(n) 空间为代价),并且 O(n) 时间足够,则可以使用 O(1) 空间执行计算,这已在还有其他答案。

真正需要的是,对于 test(n-1) 的任何递归计算,test(n-2)test(n-3) 的值也应该可用。如果您不喜欢创建辅助递归函数来跟踪此信息,那么下面是使用单个递归函数的替代方法。

int test(int n) {

    typedef struct { int x[2]; } state;
    static const state init = { 0, 1 };
    static state last;

    if (n > 0) {
        last = init;
        return test(-n);
    }

    n = -n;
    if (n < 3) return n;

    // After the below call, last has test(n-2) and test(n-3)
    int x = test(-(n-1));
    int result = x + last.x[0] + last.x[1];
    last.x[0] = last.x[1];
    last.x[1] = x;
    return result;
}

Try it online!

【讨论】:

    【解决方案2】:

    是时候拿出尾递归锤了

    static int testk(int i, int n, int a, int b, int c)
    {
       if (i == n) return a + b + c;
       return testk(i + 1, n, a + b + c, a, b);
    }
    
    int test(int n) {
        if (n < 2) return n;
        return testk(3, n, 2, 1, 0);
    }
    

    这是 anatolyg 对尾递归的回答的直接翻译。

    【讨论】:

      【解决方案3】:

      如果你想继续使用递归,你必须使用memoization,或者缓存。这个想法是,在计算结果时,将其存储起来以备后用。

      我建议有一个足够大的数组,比如 100 个条目(看到数字增长比 2n 慢一点,这应该足以表示任何 64 位数字)。将其初始化为 0:

      int test(int n)
      {
          static int cache[100] = {0};
          ...
      }
      

      计算出结果后,将其存储在正确的索引中:

      int test(int n)
      {
          ...
          result = ...
          cache[n] = result;
          return result;
      }
      

      但在应用公式之前,请检查缓存是否包含结果:

      int test(int n)
      {
          ...
          if (cache[n] != 0)
              result = cache[n];
          ...
      }
      

      【讨论】:

      • 大概你会用static int cache[100] = {0, 1, 2};初始化它以简化问题(根本不需要检查n &lt;= 2;如果它是肯定的,它在缓存中,或者你可以从缓存中计算它)。
      • @ShadowRanger:不检查n &lt;= 2会导致问题,因为该系列的第0项为零,这也用于指示尚未设置缓存值。
      • @EricPostpischil:是的。我建议将 -1 作为标记,但是将一个大型静态数组初始化为除 0s 之外的任何东西都是一件麻烦事。
      【解决方案4】:

      假设你想找到test(10)。作为人类,您自然会找到算法来找到它。开始制作结果表。

      n = 0 -- result is 0
      n = 1 -- result is 1
      n = 2 -- result is 2
      

      要继续表格,只需使用表格中的最后几个数字:

      n = 0 -- result is 0
      n = 1 -- result is 1
      n = 2 -- result is 2
      n = 3 -- result is 3
      n = 4 -- result is 6
      ...
      

      算法是“取最后 3 个数字并将它们相加,然后将答案写在表格的新行中”。这很容易实现。像这样的:

      n1 = 2
      n2 = 1
      n3 = 0
      for i from 3 to n
          // Imagine you have everything up to i written on paper.
          // Here n1 is the last number, n2 is before that, n3 is before that.
          // Try to continue writing your table.
      
          result = n1 + n2 + n3
      
          // Now imagine you have written the result on paper.
          // What are the 3 last numbers now?
      
          n3 = n2
          n2 = n1
          n1 = result
      
          // Verify that in a debugger for better visualization.
      
      // Now, after the loop is finished, n1 (the last number) is the correct answer.
      
      return n1
      

      【讨论】:

      • 谢谢,我知道如何使用循环来处理它,但限制是只能使用递归来处理它。
      猜你喜欢
      • 1970-01-01
      • 2018-03-31
      • 2018-05-14
      • 2016-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-25
      相关资源
      最近更新 更多