浏览 1740 次
|
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
|---|---|
| 作者 | 正文 |
|
最后更新时间:2006-09-11
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。 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |



