Tail Recursion = 循环 = CPS
程序员文章站
2022-07-06 19:57:33
...
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。
简单的不说了。只说复杂的,需要重用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。
下一篇: springmvc多线程并发的问题