栈与递归的实现

Published: by Creative Commons Licence

递归函数:一个直接调用自己或通过一些列的调用语句间接地调用自己的函数

递归是程序设计中一个强有力的工具,有下面三种类型:

  1. 递归定义的数学函数;
  2. 有的数据结构,如二叉树、广义表等,由于结构本身故有的递归特性,则他们的操作可以递归地描述;
  3. 虽然问题本身没有明显的递归结构,但用递归求解比迭代求解更简单,如八皇后问题、Hanoi塔问题。

n阶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上(依照前法);

void hanoi(int n, char x, char y, char z) {
	// 将塔座x上按直径由大到小且自上而下编号为1至n的n个圆盘按规
	// 搬到塔座z上,y可用作辅助塔座。
	// move(x, n, z):将编号为n的圆盘从x移动到z
	if(n == 1) move(x, 1, z);	// 将编号为1的圆盘从x移动到z
	else {
		hanoi(n-1, x, z, y);	// 将x上编号为1至n-1的圆盘移动到y,z作辅助塔
		move(x, n, z);			// 将编号为n的圆盘从x移动到z
		hanoi(n-1, y, x, z);	// 将y上编号为1至n-1的圆盘移动到z,x作辅助塔
	}
}

关于函数调用

通常,当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需先完成三件事:

  1. 将所有的实在参数、返回地址等信息传递给被调用函数保存;
  2. 为被调用函数的局部变量分配存储区
  3. 控制转移到被调用函数的入口

而从被调用函数返回调用函数之前,系统也要完成下面三件事情:

  1. 保存被调函数的计算结果
  2. 释放被调函数的数据区
  3. 依照被调函数保存的返回地址将控制转移到被调用函数

当有多个函数构成嵌套调用时,按照“后调用先返回”的原则,上述函数之间的信息传递控制转移必须通过“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为他在栈顶分配一个存储区,每当从一个函数退出时,就释放它的存储区,则当前正在运行的函数的数据区必在栈顶

关于递归

一个递归函数的运行过程类似多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数。

为了保证递归函数正确执行,系统需要设立一个递归工作栈,作为整个递归函数运行期间使用的数据存储区。

每一层递归所需信息构成一个“工作记录”,其中包括所有的实在参数、所有的局部变量以及上一层的返回地址。

  • 每次进入一层递归,就产生一个新的工作记录压入栈顶。
  • 每退出一层递归,就从栈顶弹出一个工作记录。

则当前执行层的工作记录必是递归工作栈栈顶的工作记录,称这个记录为活动记录,并称指示活动记录的栈顶指针为当前环境指针

在调用函数与被调函数之间不一定传递参数的值,也可以传递参数的地址。

在利用允许递归调用的语言(例如C语言)进行对递归问题的编程时,不需要用户自己而由系统来管理递归工作栈。