簡體   English   中英

如何將遞歸轉換為迭代解決方案

[英]How to convert recursive to iterative solution

我設法以遞歸的方式編寫算法:

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

那么,如何糾正我的代碼以實現自己的目標?

通過一般的轉換為迭代來解決此問題是一個壞主意。 但是,這就是您的要求。

這些都不是解決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_data到堆棧與ne0的位置,然后繼續循環。 在循環的頂部,切換到堆棧位置的頂部。

要返回:創建一個函數局部變量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無效。 將指針替換為標准向量中的偏移量,它將起作用。

應為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;
}

這是一個經典的問題。 如果給定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];

}

為了完整起見,這里是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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM