1、第八章 递 归 8.1 递归的概念 8.2 基本递归过程 8.3 递归过程的实现,定义:如果一个对象部分地包含它自己,或者利用自己定义自己的方式来定义或描述,则称这个对象是递归的(递归定义);如果一个过程直接或间接地调用自己,则称这个过程是一个递归过程。(递归算法) 组成:递归调用部分、递归终止条件,8.1 什么是递归,递归的简单例子xn=x*x*x*x (n个x连乘)xn+1=xn * xS(n)=1+2+3+(n-1)+nS(n+1)=S(n)+(n+1),以下三种情况适于用递归求解问题: 问题的定义是递归的; 问题所涉及的数据结构是递归的; 问题的解法满足递归的性质。,1、问题的定义是递
2、归的 阶乘函数、幂函数和斐波那契数列。 例1 阶乘函数的定义,求解阶乘函数的递归过程long Factorial(long n) if (n=0)return 1; /递归终止条件elsereturn n * Factorial(n-1); /递归调用过程,例2 斐波那契数列Fib(n)的定义,求解斐波那契数列的递归算法long Fib ( long n ) if ( n = 1 ) return n; else return Fib (n-1) + Fib (n-2); ,例3 二叉树的定义,二叉树是结点的有限集合,它必须满足下面的一个条件: (1)它是空集。 (2)它由一个根结点,根结点的
3、左右子树构 成,且其左右子树满足二叉树定义。,2、问题所涉及的数据结构是递归的 例1 单链表节点类的递归定义,template class Node private:Node * next;public:T data; ,例2 单链表的递归定义head=NULL头指针为head的单链表的递归定义: (1)head指向一个空结点的数据结构是一个单链表; (2)head指向一个非空结点,该结点的指针域指向一个单链表,这样的数据结构是一个单链表。,在头指针为p的单链表中搜索data值为item的结点template void Search(Node *p,T item)if(p = = NULL)c
4、err data = = item)Dealwith(p); / 通过Dealwith函数对 /该节点进行一定的处理elseSearch(p-next,item);,头指针为p的单链表中搜索链表最后一个结点并打印其数值template void Find (Node *p) if ( p next = NULL )cout p data endl;else Find ( p next ); ,3、问题的解法满足递归的性质 递归求解的基本思想(分治策略): 对于一个比较复杂的问题,如果能够把它分解为若干个相对简单的、而且解法相同或类似的子问题,那么当这些子问题获得解决时,原问题就获得解决分治策略
5、。子问题无需分解就可以直接解决时,停止分解,直接求解该子问题递归结束条件。,例1 二叉树的遍历:,算法Preorder(t) Preorder 递归遍历IF tNULL THEN ( PRINT(data(t).Preorder(left(t).Preorder(right(t) . ,归纳基础 递归出口 归纳步骤 递归调用,例2 数学归纳法证明:,因此,我们通常采用数学归纳法证明递归算法的正确性。而对于递归算法的时间复杂性分析,我们通常首先定义其时间复杂性的递归数学表达式,然后求解该递归公式。,例3 求文件中的最大元-最小元问题例4 汉诺塔(Tower of Hanoi)问题 ,8.2 基本
6、递归过程递归过程在实现时,发生递归调用:分为外部调用和内部调用。调用方式不同,返回的方式也不相同。,递归过程与实现的基本思想高级语言的编译程序中,函数调用是通过堆栈实现的; 递归函数调用是一种特殊的函数调用形式,因此也可以通过堆栈实现。,函数调用方式:,f1 f2 f3 fn,递归函数调用方式:,f(n) f(n-1) f(n-2) f(1),函数调用时执行入栈操作保存本次递归调用对应的信息 函数返回时执行出栈操作恢复上次递归调用对应的信息 一次递归调用所需要的信息表示为一个工作记录: 返回地址 参 数 (函数名、引用参数与实参等)局部变量,递归工作栈,栈顶工作记录对应当前递归调用过程,称为当
7、前活动记录。,long Factorial ( long n ) if ( n = 0 ) return 1;else return n * Factorial (n-1);RetLoc2void main ( ) int Result;Result = Factorial (4); RetLoc1,计算Factorial时活动记录的内容,F(3),F(2),F(1),F(0),递归算法的优点:递归过程结构清晰递归程序易写、易读正确性证明相对容易 递归算法的不足: 运行效率低,原因:函数调用开销(时间、空间)大会出现重复计算,例 斐波那契数列Fib(n)的定义,求解斐波那契数列的递归算法lon
8、g Fib ( long n ) if ( n = 1 ) return n; else return Fib (n-1) + Fib (n-2); ,调用次数 SumCall(k) = SumCall(k-1)+SumCall(k-2)+1 递归算法的时间复杂性 O(2k),斐波那契数列的递归调用树,计算斐波那契数列的非递归函数 long CalFib(long n) if(n = 1) return n;else long f0 = 0,f1 = 1,f = 0;for( int i = 2;i = n;i+) f = f0+f1;f0 = f1;f1 = f;return f; 非递归算
9、法的时间复杂性 O(n),8.3 递归过程的实现:堆栈与递归递归过程实现的基本思想:采用堆栈模拟和实现递归过程中子任务的产生和处理过程:产生新的子任务对应入栈操作;处理子任务对应弹栈操作;先处理的子任务后入栈;后处理的子任务先入栈。,CREATS ( S ):建立一个堆栈 S; S x : 元素 x 进栈;x S : 元素 x 出栈; StackEmpty(S): 若 S 为空,返回1.,例1 Hanoi塔问题,基本思想: 1. 借助C柱,从A柱将1至m-1号盘移至B柱; 2. 将A柱中剩下的第m号盘移至C柱; 3. 借助A柱,从B柱将1至m-1号盘移至C柱。 HANOI(m,A,B,C) H
10、ANOI(m-1,A,C,B) MOVE(A,C) HANOI(m-1,B,A,C),算法 HR(m,i,j,k) / 把原柱i上的n个圆盘移到目标柱k上,圆柱j是中间柱 HR1递归出口IF m = 1 THEN (MOVE(i,k).RETURN). HR2递归调用HR(m-l,i,k,j) MOVE(i,k) HR(m-l,j,i,k) ,递归算法的实现堆栈 保存四元组 (m,i,j,k)HR(m-l,i,k,j) MOVE(i,k) HR(m-l,j,i,k)S(m-1,j,i,k).S(l,i,j,k) S(m-1,i,k,j),Hanoi塔的迭代算法,m 是原柱上圆盘的个数 算法HI
11、(m) Hanoi塔的非递归演示 HI1建立堆栈CREATS(S) HI2堆栈初始化S(m,1,2,3) HI3利用栈实现递归WHILE NOT(StackEmpty(S)DO (n,i,j,k) SIF n = 1 THEN MOVE(i,k)ELSE(S(n-1,j,i,k).S(l,i,j,k)S(n-1,i,k,j) ,问题1:HI(m)需要多大的堆栈空间?即HI的空间复杂性是多少?问题2:m个盘子的Hanoi塔问题,需要移动多少次盘子?即HI的时间复杂性是多少?,例2 计算数组 A 中最大最小元素的算法BS 。 算法BS(A ,i ,j . fmax ,fmin) / 在数组A的第i
12、个元素到第j个元素之间寻 /找最大和最小元素,已知i j 。 BS1 递归出口IF i = j THEN ( fmaxfminAi. RETURN. )IF i = j 1 THEN( IF Ai Aj THEN (fmaxAj.fminAi . )ELSE(fmaxAi. fminAj . )RETURN).,BS2 取中值mid BS3 递归调用 BS(A,i,mid. gmax,gmin)BS(A,mid+1,j. hmax,hmin). BS4 合并fmaxmaxgmax,hmax.fminmingmin,hmin. ,递归调用示意,问题: 该递归调用过程中,子任务产生和执行的顺序是什
13、么?P193,问题求解(Problem Solving)Step 1:定义解空间Step 2:组织解空间Step 3:搜索解空间,一种搜索策略回溯(backtracking)寻找特定问题解的一种比较可靠的方法是首先列出所有候选解,然后依次检查每一个候选解,在检查完所有或部分候选解后,即可找到所需要的解。 理论上,当候选解的数量有限并且通过检查所有或部分候选解能够得到所需要的解的时候,上述方法是可行的。 对候选解进行系统检查的方法有多种,其中回溯和分枝限界法是比较常用的两种方法。,回溯的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一
14、步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。 回溯法解决的问题:货船装箱、背包、最大完备子图、旅行商和电路板排列,迷宫老鼠问题,(1,1)(1,2) (1,3)失败,回溯到(1,2) (1,1) (2,1) (3,1) (3,2) (3,3),0/1背包问题对承重量为 c 的背包进行装载. 从 n 个物品中选取装入背包的物品,每件物品i的重量为wi,价值为pi . 对于可行的背包装载,背包中物品的总重量不能超过背包的承重量,最佳装载是指所装入的物品价值最高,即 取得最大值(为1表示装载物品i,为0表示不装载物品i). 约束条件为 和 .,例:n= 3,w= ( 20 , 15 , 15 ),p= (40 ,25 , 25 )且 c= 30.,1,1,1,1,1,1,1,0,0,0,0,0,0,0,