前面阶段用栈实现了表达式求值、括号匹配以及数字转换。这一次,我们介绍栈的另一个重要级别的应用-递归
栈的重要应用是在程序设计语言中实现递归。一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称为递归函数。
递归是程序设计中强有力的工具。
递归是程序设计中一个强有力的工具。其一,很多数学函数是递归定义的,例如:
阶乘函数:
其二,有的数据结构,如二叉树、广义表等,由于结构本身固有的递归特性,则它们的操作可递归地描述。
其三,还有一类问题,虽然问题本身没有明显的递归结构,但是用递归求解比迭代求解更简单,比如八皇后问题、Hanoi塔问题。
假设有3个分别命名为X、Y、Z的塔座,在塔座X上插有n个直径大小不同、依小到大编号为1,2,……,n的圆盘,如下图。
现要求将X轴上的n个圆盘移到Z上并仍按同样的顺序叠排,圆座移动必须遵循下列规则:
(1)每次只能移动一个圆盘。
(2)圆盘可以插在X,Y,Z中的任一塔座上。
(3)任何时刻都不能将一个教大的圆盘压在较小的圆盘之上。
如何实现移动圆盘的操作呢?当 n=1 时,问题比较简单,只需要把编号为1的圆盘从塔座X直接移动到塔座Z上即可;当 n>1 时,需要利用塔座Y作为辅助塔座,若能设法将压在编号为n的圆盘之上的 n−1 个圆盘从塔座X(依照上述规则)移动至塔座Y上,则可先将编号为 n 的圆盘从塔座X移动到塔座Z上,然后再将塔座Y上的 n−1 个圆盘(依照上述法则)移动至塔座Z上。而如何将 n−1 个圆盘从一个塔座移至另一个塔座的问题是一个和原问题具有相同特征属性的问题,只是问题的规模小1,因此可以通过同样的方法求解。
int Count=0;
void move(char x, int n, char z);
void hanoi (int n, char x, char y, char z) { // 算法3.5
// 将塔座x上按直径由小到大且至上而下编号为1至n的n个圆盘按规则搬到
// 塔座z上,y可用作辅助塔座。
// 搬动操作 move (x, n, z) 可定义为:
// (c是初值为0的全局变量,对搬动计数)
// printf("%i. Move disk %i from %c to %c\n", ++c, n, x, z);
if (n==1)
move(x, 1, z); //将编号为1的圆盘从x移到z
else {
hanoi(n-1,x,z,y);
move(x, n, z); //将编号为n的圆盘从x移到z
hanoi(n-1, y, x, z); //将y上编号为1至n-1的圆盘移到z,x作辅助塔
}
}
void move(char x, int n, char z) {
printf(" %2i. Move disk %i from %c to %c\n",++Count,n,x,z);
}
上述算法显然是一个递归函数,在函数的执行函数,需要多次进行自我调用。那么,这个递归函数是如何调用的?我们先看任意两个函数直接进行调用的情形。
高级语言程序中,调用函数和被调用函数之间的链接消息及消息交互需要通过栈来进行。
通常,当一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要完成3件事情:
(1)将所有的实在参数、返回地址等信息传递给被调用函数保存。
(2)为被调函数的局部变量分配存储区。
(3)将控制转移到被调用函数的入口。
被调用函数返回调用函数之前,系统也应完成3件工作:
(1)保存被调用函数的计算结果
(2)释放被调用函数的数据区
(3)依照被调用函数保存的返回地址将控制转移到调用函数。当有多个调用构成嵌套调用时,按照“后调用先返回”的原则,上述函数之间的信息传递和控制转移必须通过“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区,每当从一个函数退出时,就释放它的储存区,则当前正运行的函数的数据区必须在栈顶。
一个递归函数的运行过程类似于多个函数的嵌套调用,只是调用函数和被调用函数都是同一个函数。所以,和每次调用相关的一个重要概念是递归函数运行的“层次”。假设调用该递归函数的主函数是第0层,则从主函数调用递归函数进入第1层;从第i层递归调用本函数为进入“下一层”,即第i+1层。反之,退出第i层递归应该返回至“上一层”,即第i-1层。为了保证递归函数正确执行,系统需设立一个“递归工作栈”作为整个递归函数运行期间使用的数据存储区。
每一层递归所需信息构成一个“工作记录”,其中包括所有的实在参数、所有的局部变量以及上一层的返回地址。每进入一层递归,就产生一个新的工作记录压入栈顶。每退出一层递归,就从栈顶弹出一个工作记录,则当前执行层的工作记录必须是递归工作栈栈顶的工作记录,称这个记录为“活动记录”,并称指示活动记录的栈顶指针为“当前环境指针”。
实际过程中,在调用函数和被调用函数之间不一定传递参数的值,也可以传递参数的地址。
void hanoi (int n, char x, char y, char z) { // 算法3.5
// 将塔座x上按直径由小到大且至上而下编号为1至n的n个圆盘按规则搬到
// 塔座z上,y可用作辅助塔座。
// 搬动操作 move (x, n, z) 可定义为:
// (c是初值为0的全局变量,对搬动计数)
// printf("%i. Move disk %i from %c to %c\n", ++c, n, x, z);
1 {
2 if (n==1)
3 move(x, 1, z); //将编号为1的圆盘从x移到z
4 else {
5 hanoi(n-1,x,z,y);
6 move(x, n, z); //将编号为n的圆盘从x移到z
7 hanoi(n-1, y, x, z); //将y上编号为1至n-1的圆盘移到z,x作辅助塔
8 }
9 }
上述算法是下图递归图解的算法描述,所为语句行,则是在上方描述所示:
接下来的内容是实现Hanoi塔以及八皇后等问题~~