簡體   English   中英

C ++中的Tail Recursion,帶有多個遞歸函數調用

[英]Tail Recursion in C++ with multiple recursive function calls

我正在閱讀關於尾遞歸的這篇文章

我將復制發布的解決方案:

unsigned int f( unsigned int a ) {
   if ( a == 0 ) {
      return a;
   }
   return f( a - 1 );   // tail recursion
}

我想知道,如果結果取決於幾個遞歸函數調用呢? 例如:

unsigned int f( unsigned int a ) {
    if ( a == 0 ) {
          return a;
       }
    return f(a -1) + f( a - 1 );   
}

上面的代碼會被編譯器優化嗎?

目前看,尾遞歸不適用。 但是如果你看一下你所鏈接問題的第二個答案的結尾,你就可以看到如何恰當地重寫這個功能。 從...開始

unsigned int f( unsigned int a ) {
    if ( a == 0 ) {
          return a;
    }
    return f(a-1) + f(a-1);   
}

重寫如下:

unsigned int f( unsigned int a ) {
    if ( a == 0 ) {
          return a;
    }
    return 2 * f(a-1);  
}

即使是現在,尾遞歸仍然不能直接應用。 我們需要確保返回嚴格地以return f(....)的形式。 再次重寫該功能:

unsigned int f( unsigned int a, unsigned int multiplicative_accumulator = 1 ) {
    if ( a == 0 ) {
          return multiplicative_accumulator * a;
    }
    return f(a-1, multiplicative_accumulator * 2 );   
}

現在,尾遞歸適用。 這使用multiplicative_accumulator的默認值(感謝@Pubby),以便第一次調用f可以簡單地為f(x) ,否則你必須寫一些f(x,1)

感謝@SteveJessop做了幾個最后的筆記:

  • f(a+1)+f(a+1)更改為2*f(a+1)是安全的,因為f沒有副作用(打印,修改堆,這種事情)。 如果f確實有副作用,則重寫無效。
  • 原文相當於(2*(2*(2*a)) (或更確切地說, (((a+a)+(a+a))+((a+a)+(a+a))) )而當前的版本更像是(((2*2)*2)*a) 。這很好,特別是對於整數,因為乘法是關聯的和分配的。但這對於float來說並不完全等價,其中你可能會得到小的舍入差異。使用浮點運算,有時a*b*c可能與c*b*a略有不同。

第二個函數不是尾遞歸的,不能用循環輕松替換,所以很可能編譯器不會這樣做。

這不是尾遞歸(函數的結果是遞歸調用的結果):在遞歸(添加)之后有一個操作要做。 有一個更復雜的轉換(取決於加法的交換性)來獲得尾遞歸:使用累加器添加輔助函數:

unsigned int f_helper(unsigned int a, unsigned int acc)
{
   if (a == 0) {
      return acc;
   }
   return f_helper(a-1, f(a-1)+acc);
}

unsigned int f(unsigned int a) {
    if (a == 0) {
          return a;
    }
    return f_helper(a-1, f(a-1));
}

你可以轉換成一個循環

unsigned int f_helper(unsigned int a, unsigned int acc)
{
   while (a != 0) {
      acc += f(a-1);
      a = a-1;
   }
   return acc;
}

unsigned int f(unsigned int a) {
    if (a == 0) {
       return a;
    }
    return f_helper(a-1, f(a-1));
}

然后把它放回f

unsigned int f( unsigned int a ) {
  if (a == 0) {
      return a;
   }
   unsigned acc = f(a-1);
   a = a-1;
   while (a != 0) {
      acc += f(a-1);
      a = a-1;
   }
   return acc;
}

我希望它不會像其他人所說的那樣進行優化,但通常可以通過使用memoization來解決這些類型的問題,memoization使用內存而不是進行另一次遞歸調用。

有關使用C ++的一個很好的解釋,您可以查看http://marknelson.us/2007/08/01/memoization/

在您的示例中,最后一個呼叫可以替換為

return 2 * f(a - 1);

這將是可以優化的。

在很多情況下,尾部遞歸似乎不起作用,可能只需要查看算法,並進行一些更改以幫助實現此優化。

暫無
暫無

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

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