大一上学期第一次接触到“汉诺塔”问题,当时由于C语言程序设计课没有好好听(毕竟当时天真地觉得自己以后应该不会从事与程序密切相关的工作,所以经常翘课doge),对于这一类需要利用递归等算法思维进行求解的问题自然也理解的不是很到位。暑假开始学习数据结构与算法,在分治与递归这一块卡了很久,想在这一篇博客里记录一下自己关于分治与递归的一些想法。
一、分治与递归:
先简单复述一下分治的概念:
关于分治的时间复杂度:
由于递归本质上是实现分治中分解与归并的手段,因而在计算分治的复杂度时,需要知道递归方程,再由递归方程进行推算。具体方法如上图所示。
下面给出一个由递归方程计算时间复杂度的例子:
这种方法可以将原来求最大子序和一般方法平方阶的复杂度降为线性对数阶nlogn(不知道这样做是不是完全正确)
从上面的例子可以看出,一般来说,第一项等比数列求和得到的复杂度都是线性阶O(n),所以最终算法的复杂度,主要取决于第二项计算出来的阶数,也即f(n)的形式。
二、汉诺塔问题:
关于汉诺塔问题的背景我这里就不再赘述,只简单讲一下目标:有若干盘片,自上而下从小到大依次码放在一根柱子(A柱)上,现提供另外两根柱子(B、C柱),要借助这两根柱子将原来的所有盘片原封不动地移到B柱上,而且移动过程中要保证所有柱上的任何位置,盘片都是上小下大。
先来讨论一下移动次数的问题:
(之后的每一次递归,模式都与上述过程相同,只是所要移动到的柱子换了一根)
从上图中分析可知,每次递归调用,所增加的只有中间的②步骤,其余则归入下一次的递归调用。该递归过程的递归方程为:
经过迭代,可知对于n个盘片,一共需要移动2^n - 1次才能达到目标,故汉诺塔问题的时间复杂度为:
接下来是用C语言实现的代码部分,不过在放代码之前,我先就3个盘片的情况大致写了这样的一个情景模拟,更加生动形象地还原了程序内部进行盘片移动的过程:
代码如下:
#include<stdio.h>
#include<time.h>
static int count = 0;
void Hanoi(char fromwhere, char towhere, char another, int brick_num)
{
/*四个参数,brick_num表示上面要移动的(最大)片序号
fromwhere告诉上面的片,要从哪个柱子开始移
towhere告诉上面(最大)的片要移到哪个柱子上去
another为剩下的一根柱子,用作下一次递归的目标柱*/
/*当前仅有一片时,可以自由地移动*/
if (brick_num == 1)
{
printf("brick %d: %c → %c\n", brick_num, fromwhere, towhere);
count++;
}
else
{
/*在逐片往上开始进行移动方法指示,即一次连续的递归下潜时,fromwhere对应
的柱是不变的(所有片还只是在转达指令,并未开始移动)*/
Hanoi(fromwhere, another, towhere, brick_num - 1);
/*待上面所有的片全都清空之后,便可以移动当前调用的这一片*/
printf("brick %d: %c → %c\n", brick_num, fromwhere, towhere);
count++;
/*之后要开始将原来处于当前调用片上方所有的片还原,现在应该要从之前它们
所移动到的another柱将他们移动到当前片所处的towhere柱*/
Hanoi(another, towhere, fromwhere, brick_num - 1);
}
}
int main(void)
{
printf("请输入需要移动的片数:");
int brick_num = 0;
scanf("%d", &brick_num);
/*吞掉scanf没有读进去的回车*/
getchar();
printf("请输入要将这些片从哪根柱子移动到哪根柱子(一共A、B、C三根):");
char fromwhere, towhere = 0;
fromwhere = getchar();
towhere = getchar();
char another = 0;
another = fromwhere != 'C' && towhere != 'C' ? 'C' : fromwhere != 'B' && towhere != 'B' ? 'B' : 'A';
/*计算一下程序执行时间*/
clock_t start, end;
start = clock();
Hanoi(fromwhere, towhere, another, brick_num);
end = clock();
double time_s = (double)(end - start) / CLOCKS_PER_SEC;
printf("一共移动了%d次,共耗时%lf秒", count, time_s);
return 0;
}
运行结果:
总结:
其实之前我一直对于像递归这样要动点脑筋才能想清楚的算法问题比较头痛与反感,不过我相信万事开头难,只要真正沉下心去学了,感觉到自己有所收获,就会有更多的动力让自己坚持下去。
(P.S. 不知道暑假结束之前能不能考完科目二)