递归
这一篇才介绍到《SICP》第一章的第二小节。但是递归概念如此重要而又难以理解,其实看前两遍对还是没有完全把握,每每看到这里总有想逃的心理。要想掌握就要正面面对了。
递归与迭代的区别
递归过程与递归计算过程还是有区别的。递归过程是从形式上看这个过程定义时判断条件是终止还是调用自己。但是实质的计算过程可能是迭代的。计算过程表明了这个过程的局部演化方式。递归计算过程的一个特点是有延迟操作,这时堆栈需要保持每一步的计算状态,待递归遇到终止条件返回后,再执行该操作,是先扩展后收缩的过程。而迭代计算过程借助局部变量保持每一步的计算结果,传递给下一步递归调用,所以它的每一步都是完整的,中断之后可以继续执行。递归计算过程需要系统保存更多的计算的中间状态。
递归是中从上到下的解决问题的方式,即将大的问题分解成相似的小问题,解决小问题是解决整个问题的一个必不可少的步骤。重要的有两点:
- 什么是base case,以及它的解决方法;
- 什么是recursive case,它是如何基于前一个调用的。
举例
如果要计算一个数组的前n个元素和,定义函数:sum(int[] arr,n)。你可以选择迭代法计算,但在这里说明如何将问题分解,然后递归调用sum函数完成计算。要计算sum(arr,n)的值,首先计算sum(arr,n-1)的值,然后sum(arr,n-1)+arr[n-1]得到sum(arr,n)的值。这个递归函数的终止条件是当n=0时,返回0。代码如下:
int sum( int arr[], int n )
{
if ( n == 0 ) // base case
return 0 ; // no recursive call
else
{
int small = sum( arr, n - 1 ) ; // solve smaller problem
// use solution of smaller to solve larger
return small + arr[ n - 1 ] ;
}
}
写递归函数的步骤:
- 用语言描述递归过程;
- 给每个函数过程加注释;
- 确定终止条件,并给出它的解决方法;
- 假想较小的问题是什么?它的解决方法对整个问题的解有什么用?然后判断这个问题是否适合递归求解;
- 通过求解较小问题,来解决整个问题。
其中第三步和第四步结合起来决定了递归的总体过程,也是设计递归函数的难点所在。
下面的例子是类似深度优先算法,遍历树的节点。。base case是当该节点为叶子节点时,判断该节点是否包含keyword;如果不是叶子节点,即它的childlist不为空,那么递归调用processNodeList继续处理该list,直到遇到base case(到达该树的叶子节点)。
private static void processNodeList(NodeList list, String keyword) {
//迭代开始
SimpleNodeIterator iterator = list.elements();
while (iterator.hasMoreNodes()) {
Node node = iterator.nextNode();
//得到该节点的子节点列表
NodeList childList = node.getChildren();
//孩子节点为空,说明是值节点
if (null == childList)
{
//得到值节点的值
String result = node.toPlainTextString();
//若包含关键字,则简单打印出来文本
if (result.indexOf(keyword) != -1)
System.out.println(result);
} //end if
//孩子节点不为空,继续迭代该孩子节点
else
{
processNodeList(childList, keyword);
}//end else
}//end wile
}