【问题标题】:How to convert recursive to iterative solution如何将递归转换为迭代解决方案
【发布时间】:2016-02-15 12:31:38
【问题描述】:

我已经设法以递归方式编写我的算法:

int fib(int n) {
    if(n == 1)
        return 3
    elseif (n == 2)
        return 2
    else
        return fib(n – 2) + fib(n – 1)
}

目前我正在尝试将其转换为迭代方法但没有成功:

int fib(int n) {
   int i = 0, j = 1, k, t;
   for (k = 1; k <= n; ++k)
   {
       if(n == 1) {
           j = 3;
       }
       else if(n == 2) {
           j = 2;
       }
       else {
           t = i + j;
           i = j;
           j = t;
       }
   }
   return j;
}

那么我怎样才能纠正我的代码以达到我的目标?

【问题讨论】:

  • 最好不要“转换”,而是看原来的问题,并提出一个忽略递归的迭代解决方案。
  • 你被谷歌禁止了吗?搜索“iterative fibonacci c++”你会发现很多解决方案。
  • 只是出于好奇:你为什么写if (n == 1) return 3?为什么在n == 1的情况下放3而不是1

标签: c++ c algorithm recursion fibonacci


【解决方案1】:

通过一般的转换到迭代来解决这个问题是一个坏主意。但是,这是你问的。

这些都不是解决fib 的好方法:fib 有封闭形式的解决方案,和/或更清洁的迭代解决方案,和/或递归记忆解决方案。相反,我展示了相对机械技术来获取递归函数(不是尾递归或其他简单的解决方法),并在不使用自动存储堆栈(递归)的情况下解决它。

我的代码递归嵌套太深,在中高复杂度的情况下会破坏堆栈;当重构为迭代时,问题就消失了。当您拥有的是一个您一半理解的递归解决方案并且您需要它是迭代的时,这些是所需的解决方案。


将递归转换为迭代解决方案的一般方法是手动管理堆栈。

在这种情况下,我还将记住返回值。

我们将返回值缓存在retvals中。

如果我们不能立即解决问题,我们说明我们首先需要解决哪些问题才能解决我们的问题(特别是 n-1 和 n-2 的情况)。然后我们再次排队解决我们的问题(到那时,我们将准备好我们需要的东西)。

int fib( int n ) {
  std::map< int, int > retvals {
    {1,3},
    {2,2}
  };
  std::vector<int> arg;
  arg.push_back(n);
  while( !arg.empty() ) {
    int n = arg.back();
    arg.pop_back();
    // have we solved this already?  If so, stop.
    if (retvals.count(n)>0)
      continue;
    // are we done?  If so, calculate the result:
    if (retvals.count(n-1)>0 && retvals.count(n-2)>0) {
      retvals[n] = retvals[n-1] + retvals[n-2];
      continue;
    }
    // to calculate n, first calculate n-1 and n-2:
    arg.push_back(n); arg.push_back(n-1); arg.push_back(n-2);
  }
  return retvals[n];
}

没有递归,只是一个循环。

执行此操作的“愚蠢”方法是获取函数并将其设为伪协程。

首先,重写你的递归代码,每行做一件事:

int fib(int n) {
  if(n == 1)
    return 3
  if (n == 2)
    return 2
  int a = fib(n-2);
  int b = fib(n-1);
  return a+b;
}

接下来,创建一个包含所有函数状态的结构体:

struct fib_data {
  int n, a, b, r;
};

并在我们进行递归调用的每个点添加标签,以及具有相似名称的枚举:

enum Calls {
  e1, e2
};
int fib(int n) {
  fib_data d;
  d.n = n;

  if(d.n == 1)
    return 3
  if (d.n == 2)
    return 2
  d.a = fib(n-2);
CALL1:
  d.b = fib(n-1);
CALL2:
  d.r = d.a+d.b;
  return d.r;
}

CALLS 添加到您的fib_data

接下来创建fib_data的堆栈:

enum Calls {
  e0, e1, e2
};
struct fib_data {
  Calls loc = Calls::e0;
  int n, a, b, r;
};
int fib(int n) {
  std::vector<fib_data> stack;
  stack.push_back({n});

  if(stack.back().n == 1)
    return 3
  if (stack.back().n == 2)
    return 2
  stack.back().a = fib(stack.back().n-2);
CALL1:
  stack.back().b = fib(stack.back().n-1);
CALL2:
  stack.back().r = stack.back().a + stack.back().b;
  return stack.back().r;
}

现在创建一个循环。不要递归调用,而是在您的fib_data 中设置返回位置,将fib_datane0 位置一起压入堆栈,然后继续循环。在循环的顶部,切换到栈顶的位置。

返回:创建一个函数局部变量 r 来存储返回值。要返回,请设置r,弹出堆栈,然后继续循环。

如果堆栈在循环开始时为空,则从函数返回r

enum Calls {
  e0, e1, e2
};
struct fib_data {
  int n, a, b, r;
  Calls loc = Calls::e0;
};
int fib(int n) {
  std::vector<fib_data> stack;
  stack.push_back({n});
  int r;
  while (!stack.empty()) {
    switch(stack.back().loc) {
      case e0: break;
      case e1: goto CALL1;
      case e2: goto CALL2;
    };
    if(stack.back().n == 1) {
      r = 3;
      stack.pop_back();
      continue;
    }
    if (stack.back().n == 2){
      r = 2;
      stack.pop_back();
      continue;
    }
    stack.back().loc = e1;
    stack.push_back({stack.back().n-2});
    continue;
CALL1:
    stack.back().a = r;
    stack.back().loc = e2;
    stack.push_back({stack.back().n-1});
    continue;
CALL2:
    stack.back().b = r;
    stack.back().r = stack.back().a + stack.back().b;
    r = stack.back().r;
    stack.pop_back();
    continue;
  }
}

然后请注意 br 不必在堆栈中 -- 将其删除,并使其成为本地。

这种“愚蠢”转换模拟 C++ 编译器在递归时所做的事情,但堆栈存储在空闲存储区而不是自动存储区,并且可以重新分配。

如果指向局部变量的指针需要保持不变,则对堆栈使用std::vector 将不起作用。将带有偏移量的指针替换为标准向量,就可以了。

【讨论】:

    【解决方案2】:

    这应该是 fib(0) = 0, fib(1) = 1, fib(2) = 1, fib(3) = 2, fib(4) = 3, fib(5) = 5, fib( 6) = 8, ... .

    fib(n)
    {
    int f0, f1, t;
        if(n < 2)
            return n;
        n -= 2;
        f0 = 1;
        f1 = 1;
        while(n--){
            t = f1+f0;
            f0 = f1;
            f1 = t;
        }
        return f1;        
    }
    

    或者你可以稍微展开循环,去掉 temp 变量:

    int fib(int n)
    {
    int f0, f1;
        if(n < 2)
            return n;
        f0 = 1-(n&1);
        f1 = 1;
        while(0 < (n -= 2)){
            f0 += f1;
            f1 += f0;
        }
        return f1;
    }
    

    【讨论】:

      【解决方案3】:

      这是一个经典问题。如果给定 n 并且想要向下计算,则不能简单地摆脱递归。

      解决方案是动态规划。基本上你想创建一个大小为 n 的数组,然后从索引 0 开始填充它,直到达到索引 n-1;

      类似这样的:

      int fib(int n)
      {
          int buffer[n+1];
          buffer[0]=3;
          buffer[1]=2;
          for(int i=2;i<=n; ++i)
          {
              buffer[i] = buffer[i-1] + buffer[i-2];
          }
      
          return buffer[n];
      }
      

      或者为了节省内存而不是使用可以使用的大数组:

      int fib(int n)
      {
          int buffer [2];
          buffer[0] = 3;
          buffer[1] = 2;
      
      
          for(int i=3; i<=n; i++)
          {
          int tmp = buffer[0] + buffer[1];
          buffer[0] = buffer[1];
          buffer[1] = temp;
          }
      
          return buffer[1];
      

      }

      【讨论】:

      • 嗯?迭代解决方案在这里是小菜一碟。与经典斐波那契数列的唯一区别是第一个元素(我希望它不是拼写错误)。更新:代码是如何解决这个问题的一个很好的例子。
      • 第二个版本好很多(忽略奇怪的非c数组声明语法)。
      【解决方案4】:

      为了完整起见,这里是具有 O(1) 空间复杂度的迭代解决方案:

      int fib(n)
      {
          int i;
          int a0 = 3;
          int a1 = 2;
          int tmp;
      
          if (n == 1)
              return a0;
      
          for (i = 3; i <=n; i++ )
          {
              tmp = a0 + a1;
              a0 = a1;
              a1 = tmp;
          }
          return a1;
      }
      

      【讨论】:

        猜你喜欢
        • 2020-06-07
        • 1970-01-01
        • 2019-06-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-22
        • 1970-01-01
        • 2014-08-20
        相关资源
        最近更新 更多