Tail Recursion = 循环 = CPS

Tail Recursion的一点经验

简单的不说了。只说复杂的,需要重用runtime stack上的计算结果的情况。

经验就是
tail recursion = continuation passing style。

可以看看 continuation passing style, CPS。
这个continuation其实就可以看作当前的运行栈。只是我们并不需要整个运行栈,所以,我们可以自己
把需要重用的计算结果,都包装在多出来的一个context(contiunation)参数里面,传递下去。
最复杂的情况,这个context也不过是一个stack数据结构。

比如....
写一个典型的例子吧。

General Fibonacci Sample
Fibonacci问题描述:
1头母牛,出生后第3年,就开始每年生1头母牛,按此规律,第n年时有多少头母牛。
f(1)=1
f(2)=1
f(n)=f(n-1)+f(n-2)

Fibonacci数列:1,1,2,3,5,8,13,,21,34........

Fibonacci问题变种A描述:
1头母牛,出生后第4年,就开始每年生1头母牛,按此规律,第n年时有多少头母牛。
f(1)=1
f(2)=1
f(3)=1
f(n)=f(n-1)+f(n-3)

Fibonacci数列:1, 1, 1, 2, 3, 4, 6, 9,13,19,28........

Fibonacci问题通用描述:
1头母牛,出生后第x年,就开始每年生1头母牛,按此规律,第n年时有多少头母牛。
令k = x - 1
f(1)=1

f(k)=1
f(n)=f(n-1)+f(n-k)

递归解法仍然很自然:
int fibonacci(int n, int k){
if(n <= k)
return 1;

int previousResult1 = fibonacci(n - 1);
int previousResultK= fibonacci(n – k)

int result = previousResult1 + previousResultK
return result;
}

下面把它改成Tail Recursion。这时候我们需要跟踪前k结果。不管怎么说,对于每一次执行,k还是一个固定数字。我们可以用一个k长度的数组来保存前k个中间结果,而不需要一个变长的stack结构。我们可以移动这个k长度的数组里面的数据,来存储当前需要用到的计算结果,参见move方法。(好像一个网络传输协议中的那个窗口概念一样)

int[] alloc(int k){
int[] array = new int[k];
for(int i = 0; i < k; i++){
  array[ i ] = 1;
}
return array;
}

void move(int[] array){
int k = array.length;
int limit = k – 1;
for(int i = 0; i < limit; i++){
  array[i+1] = array[ i ];
}
}

int fibonacci(int n, int k){
int[] middleResults = alloc(k);
return tail_recursive_fibonacci(1, middleResults[], n);
}

int tail_recursive_fibonacci(int currentStep, int[] middleResults, int n){
int k = middleResults.length;
if(currentStep <= k)
return 1;

if(currentStep == n)
return middleResults[0] + middleResults[k-1];

int nextStep = currentStep + 1;
int currentResult = middleResults[0] + middleResults[k-1];
move(middleResults);
middleResults[0] = currentResult;

return tail_recursive_fibonacci(nextStep, previousResult1, previousResult2);
}

下面我们把它改成循环。关键步骤还是把middleResults作为循环体外部的变量。
int fibonacci(int n, int k){
if(n == 1)
return 1;

if(n ==2)
return 1;

int[] middleResults = alloc(k);
int last = k – 1;
int result = 0;

for(int i = 3; i <= n; i++){
result = middleResults[0] + middleResults[last];
move(middleResults);
middleResults[0] = result;
}

return result;
}

注:上述写法采用了最直观的写法,并不是最俭省的写法。比如,tail_recursive_fibonacci函数的currentStep参数可以省掉;循环解法里面的最后一次循环中,计算result后,可以直接break。

你可能感兴趣的:(数据结构,F#,网络协议)