一.问题引入
简单来说,汉诺塔问题有以下几个要求:
1.有A,B,C三根柱子,在A柱上有n个大小逐渐变大的盘子,我们现在要把A柱上的所有盘子移到C柱上。同时要遵循以下两点要求。
2.一次只能移动一个盘子。
3.无论何时,小的盘子必须在大的盘子上面。
下面是3个盘子的初始状态,我也将展示3个盘子的代码
二.解决思路
从逻辑的角度来看,我们要将这个问题进行分层,得出最重要的两步:
1.移开除了最底层的盘子以外的盘子
2.移动最底层的盘子
于是我们能够解析出一个移动通式
if(n==1)直接移动
else {
step 1 将上方的n-1个盘子移动到辅助塔(非起始塔和目标塔)
step 2 移动最底层的盘子到目标塔
step 3 将移走的n-1个盘子移到目标塔
}
而这其中n-1盘子的移动方法使用递归的思想来实现
代码如下
#include <stdio.h>
/**
* Hanoi
*/
void hanoi(int paraN, char paraSource, char paraDestination, char paraTransit) {
if (paraN <= 0) {
return;
} else {
hanoi(paraN - 1, paraSource, paraTransit, paraDestination);
printf("%c -> %c \r\n", paraSource, paraDestination);
hanoi(paraN - 1, paraTransit, paraDestination, paraSource);
}// Of if
}// Of hanoi
/**
* Test the hanoi function
*/
void hanoiTest() {
printf("---- addToTest begins ----\r\n");
printf("2 plates\r\n");
hanoi(2, 'A', 'B', 'C');
printf("3 plates\r\n");
hanoi(3, 'A', 'B', 'C');
printf("---- addToTest ends ----\r\n");
}// Of addToTest
/**
The entrance
*/
void main() {
hanoiTest();
}// Of main
说明:这里的移动在计算机内部并没有进行,仅仅是用输出语句告诉我们在逻辑上进行了移动,读者可以自行去用程序设计语言去实现物理的移动。
逻辑流程图
三.内存追踪
我们将函数变为这样
void hanoi(int paraN, char paraSource, char paraDestination, char paraTransit) {
if (paraN <= 0) {
return;
} else {
printf("the address of paraN: %ld, paraSource: %ld, paraTransit :%ld\n",¶N,¶Source,¶Transit);
hanoi(paraN - 1, paraSource, paraTransit, paraDestination);
printf("%c -> %c \r\n", paraSource, paraDestination);
hanoi(paraN - 1, paraTransit, paraDestination, paraSource);
}// Of if
}// Of hanoi
在每次进行函数调用的时候进行对其中参数的地址打印,进行对地址的追踪。
下图为结果
我们研究plate= 2 的部分
the address of paraN: -1191183088, paraSource: -1191183080, paraTransit :-1191183064
the address of paraN: -1191183136, paraSource: -1191183128, paraTransit :-1191183112
A -> C
A -> B
the address of paraN: -1191183136, paraSource: -1191183128, paraTransit :-1191183112
C -> B
发现这个有两个函数的空间的参数地址是相同的,此时这两个函数在执行什么呢?
首先是第二行地址打印,这时的paraN为1,在打印出A->C的时候Hanoi(1)就已经结束了
然后是最后一行地址打印,这时的paraN为1,而在此时的参数占用地址跟上一句时一样的,为何?
这时我们就要提出一个概念叫做“递归工作栈”的概念了。
四.递归工作栈
在刚才的函数中我你能够看出在paraN=2时候的函数在工作还未进行完成时,这时又进行了paraN=1 的函数,根据函数的工作原理,在进行paraN=2 的时候,计算机已经给这个函数分配了一片的地址了,在进行到paraN的时候,我们又要分配一片空间去给paraN=1 这个函数去进行使用,在完成后,释放这片空间,进行移动操作,然后再次执行paraN=1,这时又使用了刚才的空间,这是因为递归的工作是以栈的形式完成的。
因为在执行函数中的函数的时候,我们怎么去保存上一级递归的数据呢?
我们首先要执行paraN=2,紧接着我们就要执行paraN=1,把paraN=2 的数据压入栈中。
paraN=1中又执行paraN=0,又将paraN=1 的参数列表压入栈中。
而后如果函数执行完成,比如,paraN=0,直接进行了return,此时就要将paraN=1从栈中弹出,继续进行函数的执行。
五.时间复杂度和空间复杂的分析
通过上图示,可易知空间复杂度为O(n)
而时间复杂度,比如Hanoi(2).
T(n)=T(n-1)+ 1
T(1)= 1
可解得时间复杂度为O(2的n次方)