栈的特点:后进先出LIFO(Last In First Out),我们可以拿压在弹夹中的子弹,后压进去,先打出来,而先压进去总是后打出来。当一个函数的运行期间调用另一个另一个函数时,就会用到栈这种数据结构。递归就是函数直接或间接调用自己,所以递归过程也会用到栈。
栈是一段内存空间,用来存储数据,在函数的调用过程中存储什么呢?
当一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要完成3件事
①将所有的实参、返回地址等信息传递给被调用函数保存(返回地址是指程序从函数返回时应该继续执行的地方)
②为被调用函数的局部变量分配存储区
③将控制转移到被调函数的入口
而被调用函数返回调用函数之前,系统也应完成3件事
①保存被调函数的计算结果
②释放被调函数的数据区
③依照被调函数保存的返回地址将控制转移到调用函数
下面以印度的古老传说汉诺塔问题为例分析,递归过程中栈的变化。
问题描述:
法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
抽象:
(n阶Hanoi塔问题) 假设有3个分别命名为X、Y、Z的塔柱,在塔柱X上插有n个直径大小各不相同、从小到大编号为1,2...,n的圆盘。现要求将X塔柱上的n个圆盘移至塔柱Z上并仍按同样的顺序叠排,圆盘移动时必须遵循下列规则:
①每次只能移动一个圆盘
②圆盘可以插在X、Y、Z中的任一塔柱上
③任何时刻都不能将一个较大的圆盘压在较小的圆盘上之。
以下图片为X塔柱上有三个圆盘的初始状态
算法分析:
如何实现移动圆盘的操作呢?
①当 n = 1 时 问题很简单只要将编号为1的圆盘从塔柱X直接移动到塔柱Z move(x, 1, z)
②当n > 1 时 需要利用Y作为辅助塔,若能设法将压在编号为n的圆盘上的n-1个圆盘从塔柱X移动到Y上,则先将编号为n的圆盘从塔柱X移动至塔柱Z上,然后再将塔柱Y上的n-1 个圆盘(依据上述法则)移动塔柱Z上。
而如何将n-1个圆盘从一个塔柱移至另一个塔柱的问题是一个和原问题具有相同特征属性的问题,只是问题的规模小1,因此可以用同样的方法求解
算法描述:
hanoi(n, x, y, z)
1 if n==1
2 move(x, 1, z)
3 else
4 hanoiI(n-1,x, z, y)
5 move(x,n,z)
6 hanoi(n-1,B, A, C)
程序实现:
#include <stdio.h>
int c = 0; //搬动记数
void move(char x, int n, char z) {
printf("%03d. Move disk %d from %c to %c\n", ++c, n, x, z);
}
void hanoi(int n, char x, char y, char 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作为辅助轴
}
}
int main() {
int n;
char a = 'a', b = 'b', c = 'c';
scanf_s("%d", &n);
hanoi(n, a, b, c);
return 0;
}
以下图展示以n = 3 时 hanoi(3, a, b,c ) 函数调用执行过程中递归工作栈状态的变化情况,语句行号见上图,实际上在程序执行过程中还调用了自定义函数move,和库函数printf()其栈状态忽略。用语句行号表示返回地址,(返回到哪里?接下来执行什么?),从调试跟踪来看,程序执行时函数调用后返回时,先回“出发的地方”,然后从下条语句执行 。下图中红色文字表示栈顶。
函数间调用图