《计算机程序的构造与解释》(三)

递归

    这一篇才介绍到《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 ] ;
    }
}
写递归函数的步骤:
  1. 用语言描述递归过程;
  2. 给每个函数过程加注释;
  3. 确定终止条件,并给出它的解决方法;
  4. 假想较小的问题是什么?它的解决方法对整个问题的解有什么用?然后判断这个问题是否适合递归求解;
  5. 通过求解较小问题,来解决整个问题。
其中第三步和第四步结合起来决定了递归的总体过程,也是设计递归函数的难点所在。
下面的例子是类似深度优先算法,遍历树的节点。。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
	}


1,过程作为返回值 在1.3中我们明白了高阶函数之后,“用一个过程作为另外一个过程的返回值”则是稀松平常的事情了,比如下面的代码: (define (f x) (+ x 1)) (define (g) f) ((g) 2) 函数g没有参数,其返回值为函数f,所以((g) 2)就运算结果就是(f 2),最后运算结果为3。 上面是用一个已命名的函数作为返回结果的,相应的,也可以将一个“匿名过程”作为结果返回,这里的“匿名过程”也就是我们的Lambda表达式,所以上面的代码可以改造成: (define (g) (lambda (x) (+ x 1))) ((g) 2) 那么((g) 2)的运算结果就是((lambda (x) (+ x 1)) 2),最后运算结果为3。 2,牛顿法 学到这里,你可能需要复习一下高等数学的基本内容,包括“导数”和“微分”,高数的在线教材可以在这里找到:http://sxyd.sdut.edu.cn/gaoshu1/index.htm 关于牛顿法的介绍可以看这里:http://en.wikipedia.org/wiki/Newton%27s_method ,下面是程序: (define (close-enough? v1 v2) (< (abs (- v1 v2)) 0.000000001)) ;定义不动点函数 (define (fixed-point f first-guess) (define (try guess step-count) (let ((next (f guess))) (if (close-enough? guess next) next (try next (+ step-count 1))))) (try first-guess 0)) ;定义导数函数 (define (D f) (lambda (x dx) (/ (- (f (+ x dx)) (f x)) dx))) ;牛顿法 (define (newton g first-guess) (fixed-point (lambda (x) (- x (/ (g x) ((D g) x 0.000000001)))) first-guess)) ;平方 (define (square x) (* x x)) ;定义开方,来测试下牛顿法 (define (sq x) (newton (lambda (y) (- (square y) x)) 1.0)) (sq 5) 3,“一等公民” 这里列出了程序语言中作为“一等公民”的语言元素所具备的几个“特权”: 可以用变量命名 可以作为过程参数 可以作为过程返回结果 可以被包含在数据结构中 4,练习1.40 求次方程 x^3 + ax^2 + bx + c 的零点。 首先,证明 函数f(x) = x^3 + ax^2 + bx + c 是“可微”的: 由可导和可微的性质知道,可导和可微互为充要条件,所以,要证可微我们可以先证可导, f ’ (x) = (x^3)’ + (ax^2)’ + (bx)’ + (c)’ = 3x^2 + 2ax + b 所以f(x)的导数存在,那么f(x)可导,其必定可微。 其次,利用“牛顿法”:如果f(x)是可微函数,那么f(x)=0的一个解就是函数(x – f(x)/df(x)的一个不动点,其中df(x)是f(x)的导数。所以我们可以轻松得到下面的代码: (define (close-enough? v1 v2) (< (abs (- v1 v2)) 0.000000001)) ;定义不动点函数 (define (fixed-point f first-guess) (define (try guess step-count) (let ((next (f guess))) (if (close-enough? guess next) next (try next (+ step-count 1))))) (try first-guess 0)) ;定义导数函数 (define (D f) (lambda (x dx) (/ (- (f (+ x dx)) (f x)) dx))) ;牛顿法 (define (newton g first-guess) (fixed-point (lambda (x) (- x (/ (g x) ((D g) x 0.000000001)))) first-guess)) ;定义cubic函数,也就是我们题目中所谓的f(x) (define (cubic a b c) (lambda (x) (+ (* x x x) (* a x x) (* b x) c))) ;随便定义几个系数 (define a 3) (define b 5) (define c 8) (define result (newton (cubic a b c) 1.0)) ;定义一个验证过程,让其验证得到的解,是否让方程成立 (define (validate x) (= 0 (+ (* x x x) (* a x x) (* b x) c))) ;输出结果 result ;验证结果 (validate result) 比如上面我们计算 x^3 + 3x^2 + 5x + 8 = 0, 其一个解为:-2.3282688556686084 .....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值