簡體   English   中英

將遞歸函數轉換為尾遞歸函數

[英]Convert a recursive function to a tail recursive function

連續這個問題:

我有一個c ++函數,一遍又一遍地調用自己。 就是這個:

#include <iostream>
#include <math.h>
#include <stdio.h>
#include <cmath>
using namespace std;
double g( double a, double x){
    if (x>=a) return (g(a,x-1)+g(a,x-a));
    else if (x<a) return 1;
    return 0; //Never Reached
}
int main(){
    cout << (unsigned long)g(sqrt(90),90) <<endl; // outputs 7564511
    cout << (unsigned long)g(sqrt(10000019),10000019)<<endl; // Signal: SIGSEGV (Segmentation fault)
}

我想知道如何將此函數轉換為某種迭代或尾循環(或任何阻止段錯誤的東西),但更重要的是我需要知道如何自己實際執行此操作。


注意:如果這是一個微不足道的問題,我會提前道歉。

注2:有類似的問題(比如這個或者這個 ),但是我發現的問題都沒有說明我的函數每次迭代都會調用兩次。

記憶可以是限制計算所需的遞歸調用次數的有效方法。 嘗試評估一些簡單輸入的函數,比如g(2, 8) ,你會看到你最終一遍又一遍地評估相同值的函數。 通過在第一次計算時為每組輸入緩存結果,可以使遞歸短路並顯着減小問題的大小。

為了使函數迭代而不是遞歸,您可以使用的一種策略是嘗試轉換定義並從下往上迭代。 考慮Fibonacci函數:

fib(n) = fib(n-1) + fib(n-2)

為了迭代地計算fib(n),我們從基礎情況fib(1) + fib(0)並迭代到fib(n) 這使您可以隨時累積值,而不必在計算中間值時記住您的位置(一遍又一遍)。 所以fib()的迭代定義如下:

fib(n) {
    a = 1;
    b = 0;
    fib = 0;
    i = 1;
    while (i < n) {
        fib = a + b;
        b = a;
        a = fib;
        i++;
    }
    return fib;
}

您應該可以使用g()函數執行類似的操作。 我沒有時間玩它,但我敢打賭,如果你嘗試手動評估幾個a, x對,你會注意到一個模式,它會讓你以迭代的形式重寫函數我的方式對於fib()做了以上。

正如許多人已經說過的那樣,這不能直接轉換為尾遞歸或迭代函數,因為浮點函數參數使迭代構建結果變得困難。 但是,通過一些思考,可以非常有效地計算函數。

首先,因為所有遞歸都是求和的並以1結尾,所以該函數基本上計算了遞歸結束的路徑數。 例如,對於g(5,2),一條路徑是g(2,5) - > g(2,3) - > g(2,1)(這里返回1)和另一條路徑g(5, 2) - > g(4,2) - > g(3,2) - > g(2,2) - > g(0,2)。 因此,為了計算g,我們需要計算可能路徑的數量。

讓我們從我們總是減去x的路徑開始。 顯然,我們只有一條這樣的道路。 接下來,考慮我們一旦選擇減去1的路徑和減去a的其他時間的情況,我們有樓層((xa)/ a)位置來選擇1.因此,有樓層((xa)/ a )在這種情況下可能的路徑。 在下一次迭代中,我們想要選擇步驟2兩次。 存在n *(n-1)/ 2組合,其中n = floor((x-1-a)/ a)並且n *(n-1)/ 2是二項式系數\\ binom {n,2}。 下一步有三個,有\\ binom {n,3}組合,其中n現在= floor((x-2-a)/ a)等。

如果預先計算二項式系數,則算法為O(x),因此它也可能計算g(sqrt(10000019),10000019)。 但是,最大的本機c ++整數類型(unsigned long long)溢出已經在g(sqrt(500),500)附近。 您可以使用long double來獲得稍大輸入的近似值。 或者你可以使用boost Multiprecision Library來獲得更多的數字,但我猜你在得到g(sqrt(10000019),10000019)的答案之前會耗盡內存。

帶有溢出檢查的源代碼來計算g()

#include <iostream>
#include <vector>
#include <limits>
#include <algorithm>
#include <cstdlib>

unsigned long long binomial(unsigned int n, unsigned int m) {
  if (n - m < m) m = n - m;
  std::vector<unsigned long long>bc(m+1, 0);
  bc[0] = 1;
  for (unsigned int i = 0;i <= n;++i) {
    for (unsigned int j = std::min(i, m);j > 0;j--) {
      if (std::numeric_limits<unsigned long long>::max()-bc[j-1] < bc[j]) {
        std::cout << "Error: too large binomial coefficient(" << n << "," << m << ")" << std::endl;
        exit(1);
      }
      bc[j] += bc[j - 1];
    }   
  }
  return bc[m];
}

unsigned long long g(double a, double x) {
  unsigned long long r = 1;
  int n = 0;
  for (int i = static_cast<int>(x);i >= a;--i) {
    ++n;
    int m = static_cast<int>((i - a) / a);
    unsigned long long b = binomial(m + n, n);
    if (std::numeric_limits<unsigned long long>::max() - b < r) {
      std::cout << "Error: too large sum " << b << "+" << r << std::endl;
      exit(1);
    }
    r += b;
  }
  return r;
}

int main(){
  std::cout << g(sqrt(90), 90) << std::endl;
  std::cout << g(sqrt(10000019), 10000019) << std::endl;
}

我想僅供參考,這里的實現沒有遞歸。 我不確定它是否會以給定的輸入完成:

#include <stack>
double gnr(double a, double x) {
   std::stack<double> stack;
   double result = 0;
   stack.push(x);
   while (!stack.empty()) {
      x = stack.top();
      stack.pop();
      //cout << stack.size() << " " << x << endl;
      if (x < a) {
         result++;
      } else {
         stack.push(x - 1);
         stack.push(x - a);
      }
   }
   return result;
}

暫無
暫無

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

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