汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
一个简单的示意图就是这样,看起来是不是有迹可循呢?
法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。
假设有n片,移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1。那么f(n)=2^n-1。n=64时,假设每秒钟一次,共需多长时间呢?一个平年365天有31536000秒,闰年366天有31622400秒,平均每年31557600秒,写代码计算一下:
def f(n):
if n==0:
return 0
else:
return 2*f(n-1)+1
x=int(input("请输入片的个数:"))
print("需要移动",f(x),"次")
其实算法非常简单,当盘子的个数为n时,移动的次数应等于2^n – 1。后来一位美国学者发现一种出人意料的简单方法,只要轮流进行两步操作就可以了。首先把三根柱子按顺序排成品字型,把所有的圆盘按从大到小的顺序放在柱子A上,根据圆盘的数量确定柱子的排放顺序:若n为偶数,按顺时针方向依次摆放 A B C;
若n为奇数,按顺时针方向依次摆放 A C B。
⑴按顺时针方向把圆盘1从现在的柱子移动到下一根柱子,即当n为偶数时,若圆盘1在柱子A,则把它移动到B;若圆盘1在柱子B,则把它移动到C;若圆盘1在柱子C,则把它移动到A。
⑵接着,把另外两根柱子上可以移动的圆盘移动到新的柱子上。即把非空柱子上的圆盘移动到空柱子上,当两根柱子都非空时,移动较小的圆盘。这一步没有明确规定移动哪个圆盘,你可能以为会有多种可能性,其实不然,可实施的行动是唯一的。
⑶反复进行⑴⑵操作,最后就能按规定完成汉诺塔的移动。
所以结果非常简单,就是按照移动规则向一个方向移动金片:
如3阶汉诺塔的移动:A→C,A→B,C→B,A→C,B→A,B→C,A→C
接下来我们写程序演示一下3个塔环时的移动情况:
def hanoi(n, a, b, c):
if n == 1:
print(a, '-->', c)
else:
hanoi(n - 1, a, c, b)
print(a, '-->', c)
hanoi(n - 1, b, a, c)
# 调用
hanoi(5, 'A', 'B', 'C')
原理和算法以及验证都通过了,那我们就开发一个命令行版本的汉诺塔游戏吧!
left = list()
center = list()
right = list()
因为三个塔之间的数据变换不是很复杂,所以直接使用list就好
def init():
"""
初始化函数
"""
size = input("(请输入整数)请输入层数:")
# 初始化塔列表,如5层 左边塔放 1-3-5-7-9,中间和右边放5个-1
for i in range(1, int(size) + 1):
left.append(i*2-1)
center.append(-1)
right.append(-1)
return int(size)
这个地方封装了一个函数,初始化塔使用,没有返回值。
然后用了append的方法,来初始化塔的数量,最后返回不返回都可以,因为是个过程
def printStyling(i, size, ta):
"""
打印样式函数
"""
if ta[i] != -1:
# 打印前空格
for kong in range(int(size - (ta[i] - 1) / 2)):
print(" ", end="")
# 打印塔元素
for le in range(ta[i]):
print("X", end="")
# 打印后空格
for kong in range(int(size - (ta[i] - 1) / 2)):
print(" ", end="")
# 左塔这一层为空格
else:
# 打印前面空格
for kong in range(size):
print(" ", end="")
# 打印中间的棒棒
print("|", end="")
# 打印后面的空格
for kong in range(size):
print(" ", end="")
这个是打印我们的塔的样式函数,送入三个参数。
这个函数相当于绘制一帧,下面会在它的基础上,再封装一次,使其将绘制结果打印到命令行终端。
结果
def show(size):
"""控制台打印结果"""
# 修饰
print("-"*35)
# 循环层数等于size
for i in range(size):
# 打印左边塔
printStyling(i, size, left)
# 打印中间塔
printStyling(i, size, center)
# 打印右边塔
printStyling(i, size, right)
# 每行打印一个换行
print()
# 修饰
print("-" * 35)
嘿,这个就是我们的展示函数了~
def judge(takeOff, putOn, size, tSize, pSize, count):
"""判断可不可以移动
takeOff减少,putOn增加,size层数,tSize和pSize剩余空间
"""
# 如果左塔的空间空的,就是没有元素可移动
if takeOff == size:
print("操作无效!")
return 0
# 如果中塔为空,可以移动
if pSize == size:
# 中间的最后一个元素赋上左塔的第一个元素的值
putOn[pSize - 1] = takeOff[tSize]
# 左塔的第一个元素赋值-1
takeOff[tSize] = -1
# 左塔的剩余空间+1
tSize += 1
# 中塔的剩余空间-1
pSize -= 1
# 步数+1
count += 1
# 移动成功,返回剩余空间和步数
return tSize, pSize, count
# 如果中塔最上方元素比左塔最上方元素大,即可以移动
elif putOn[pSize] > takeOff[tSize]:
# 中塔当前最上方元素的再上一个元素(-1)赋上左塔最上方元素的值
putOn[pSize - 1] = takeOff[tSize]
# 左塔最上方元素赋值-1
takeOff[tSize] = -1
# 左塔剩余空间+1
tSize += 1
# 中塔剩余空间-1
pSize -= 1
# 数+1
count += 1
# 移动成功,返回剩余空间和步数
return tSize, pSize, count
# 否则不可以移动
else:
print("操作无效!")
return 0
这段代码是最后技术含量的,是我们主要的逻辑代码
def main():
"""
主要运行函数
"""
# 初始化游戏
size = init()
# 存放最初的盘剩余空间 lSize左塔 cSize中塔 rSize
lSize = 0
cSize = size
rSize = size
# 存放操作步数
count = 0
# 打印游戏介绍
print("将左塔完整地移到右塔就是胜利!")
print("左-1 中-2 右-3 退出请输入:quit")
print('例如输入:"1-2"就是将左塔的最上元素放到中
print("%d层的最佳步数是%d" % (size, pow(2, size)
# 游戏进行
while True:
print("当前移动了%d步" % (count))
# 显示当前塔的状态
show(size)
# 判断右塔是否没有剩余空间,没有即胜利,并退出
if rSize == 0:
if count == pow(2, size)-1:
print("恭喜你使用最少步数完成汉诺塔!
else:
print("恭喜你只移动了%d步完成汉诺塔
break
# 获取玩家操作
select = input("请操作:")
# 左塔移中塔
if select == "1-2":
result = judge(left, center, size, lSize
if result == 0:
continue
else:
lSize, cSize, count = result
# 左塔移右塔,下面同样
elif select == "1-3":
result = judge(left, right, size, lSize,
if result == 0:
continue
else:
lSize, rSize, count = result
elif select == "2-1":
result = judge(center, left, size, cSize
if result == 0:
continue
else:
cSize, lSize, count = result
elif select == "2-3":
result = judge(center, right, size, cSiz
if result == 0:
continue
else:
cSize, rSize, count = result
elif select == "3-1":
result = judge(right, left, size, rSize,
if result == 0:
continue
else:
rSize, lSize, count = result
elif select == "3-2":
result = judge(right, center, size, rSiz
if result == 0:
continue
else:
rSize, cSize, count = result
# 输入quit退出游戏
elif select == "quit":
break
# 如果输入的是其他不识别的文字,就拜拜
else:
print("操作有误!")
continue
main()
最后使用一个main函数来将整个子函数串联在一起。
在附件内有完整的源程序,打开可以打开在mind+内学习和运行
附件
评论