数据结构第九篇——栈与递归
栈还有一个重要应用是在程序设计中实现递归。递归是计算机 科学和数学中一种解决问题的及其重要的方法。在数据结构中,可以用它来设计简单。易于理解的算法,特别是在一些具有递归定义的结构上设计算法。
递归的概念
一个直接或间接地调用自己的函数,称作递归函数。递归是程序设计中一个强有力的方法。
递归函数和运行时栈
栈还有一个重要应用就是在程序设计语言中实现函数调用。当一个函数在运行期间调用另一个函数时,在运行被调用函数之前,系统需要将实际参数和返回值地址等数据传递给被调函数,当函数调用时,这些数据与局部变量一起构成一条“工作记录”,被压入系统提供的栈。
下面用一个简单的阶乘函数的执行过程来说明递归与栈的关系。描述如下:
1 int fact(int w) 2 { 3 if(w==0) 4 return 1; 5 else 6 t=fact(w-1) 7 return (w*t); 8 }
递归算法的设计方法
递归时解决复杂问题的一种有效方法。如果掌握了递归算法的设计思路,就能比较容易地解决一类复杂的问题。但是什么样的问题可以用递归来解决呢?如何设计递归算法呢?
一般来说,如果一个复杂的问题能够被分解成若干个同类型的子问题,那么这个问题可以用递归算法实现。
在设计递归算法时,应该遵循下面的规则:
①定义一个最小规模的问题,并给出其解。
②把复杂的问题划分为同类型的若干规模较小的子问题 ,并分别解决子问题。
③把各子问题旳解组合起来,即可得到原问题的解。
例如:汉诺塔问题:
1 void Hanoi(int n,char A,char B,char C) 2 { 3 if(n>0) //n=0是最小子问题,不做任何动作,直接退出 4 { 5 Hanoi(n-1,A,C,B); //子问题①:将A上面的n-1个盘子移到B上 6 Move(A,n.C); //将编号为n的圆盘从A移到C 7 Hanoi(n-1,B,A,C); //子问题②:将B上的n-1个盘子移到C上 8 } 9 }
递归算法的分析
利用递归使我们设计算法和编程都非常简单,可读性高。但是往往导致算法效率比较低。
这体现了算法的简单性与效率之间的矛盾。
用Fibonacci数列来说明这个问题。
1 long Fib(int n) 2 { 3 if(n==1||n==2) 4 return 1; 5 else 6 return Fib(n-1)+Fib(n-2); 7 }
从调用情况来看,相同实参的同一个函数被调用了多次。求Fib(6)时,Fib(3)被调用了3次,Fib(2)被调用了5次,总共调用的次数为15次。算法时间复杂度为O(2n)。
为了便于比较,下面给出非递归算法解决Fibonacci问题。
1 long FibIter(int n) 2 { 3 long oneback,twoback,current; 4 oneback=1; 5 twoback=1; 6 7 if(n==1||n==2) 8 return 1; 9 else 10 { 11 for(int i=0;i<n;i++) 12 { 13 current=oneback+twoback; 14 twoback=oneback; 15 oneback=current; 16 } 17 } 18 return current; 19 }
很显然,以上算法当n>=3时,时间复杂度是n-2,远小于递归算法的时间复杂度。
需要注意的是,在使用递归时必须权衡设计简单与算法的时间和空间复杂度的关系,再有足够并且效率要求不高的情况下可以使用递归。