汉诺塔问题是一个经典的“重复问题“(recurrent problem),解法也中所周知,最少移动步骤是2^n - 1。
然而,我发现,对这个问题进行进一步的研究也是挺有意思的。
本篇博客目前阐述三个Hanoi相关的三个问题(基本问题,扩展问题,变种问题)。
基本问题:
n个盘子,ABC三个地点,将A上的n个盘子移动到C上。最少的步骤是多少?
AC[n] = AB[n-1] + AC[1] + BC[n-1]
因为AC[n] = BC[n] = AB[n] := T(n)
所以以上等式可以写成T(n) = T(n-1) + 1 + T(n-1),从而得到T(n) = 2^n - 1
扩展问题
由基本问题的推导步骤,我们知道,要在2^n - 1步内将n个盘子从A移动到C,那么每一步都是固定的!
由此,产生以下问题:
经过k步移动后,每个盘子的状态是什么?即,每个盘子是在A,B还是C上?
解法1: 对于这个问题,我们当然可以模拟K步移动来解的答案,但是,这是非常没有效率的。因为K有可能很大,是2的指数级增长东西,所以,这种解法非常愚蠢!
我们需要得到的信息是每个盘子的状态,而不是每一步的具体移动步骤和状态,所以,我们需求的信息是相对较少的,也就必然有比解法1要好的多的解法。理想状况是O(n)时间复杂度,因为不可能比O(n)更小了,你需要要对看一看每个盘子。
从基本问题的推导中,我们可以看到,汉诺塔问题的解决,不过是在不断的重复和交换“起始点“ ”中间点“ 和 “目的点“。我们以src, med, 和dst来标志他们。由此,我们很容易想到,我们的解法也可以是“重复"+"交换".
我的思路是先考察简单情况,比如盘子是2个,3个的情况,得到一个先验的结论和体会。有兴趣的朋友可以试一下。
在考察完简单情况后,结合我们的大方向"重复"+"交换",我们很容易想到以下解法(如果你自己动手考察过简单情况的话,会比较容易理解):
K = (a[n-1] a[n-2] ... a[1] a[0])的二进制表示. 其中a[i] = 0或者1. (这一步需要花费O(n)时间)
for i <- n-1 to 0
if a[i] == 0
p[i] = src; swap(med, dst);
if a[i] == 1
p[i] = dst; swap(src, med);
结束。
p[n-1] ... p[0]中的值就是各个盘子的状态,算法时间复杂度为O(n).
变种问题:
变种问题是,如果盘子的移动只能从A->B->C或者C->B->A,AC之间不能直接移动盘子,这个汉诺塔的问题会怎么样呢?我们同样考察以上两个问题,即最少步骤和第K步状态。
最小步骤的推导方法和基本汉诺塔问题一致,在此不赘述了,可以得到结论是3^n - 1.
关于第K步状态,有点不一样。如果我们和基本汉诺塔问题的K步问题一样的方法来考察这个问题的话,是可以得到结论的,但是,思路复杂并且耗时长(不信的朋友可以试试)。
以下介绍一种nb的思路来解决这个问题,这个思路我自己没想出来,是参考了一些资料才得到的。
用3进制Gray Code来模拟移动过程!!!!!
朋友!你能想到么?增加了附加的条件后,数学模型竟然更加简单了!
于是移动过程就是Gray Code从00000 增加到 222222 , 于是刚好可以用0 1 2来表征状态!!!有木有!!!
第K步的状态可以用3进制来表示后,设一个reverse flag(表征某位是从0-1-2还是2-1-0)。于是便可以用简单的数学模型+重复+[每步reverse flag]来解决问题!时间复杂度为O(n).
3进制gray code!!
路漫漫其修远兮,吾将上下而求索!