Python数据结构与算法——第八课 汉诺塔

    汉诺塔(又称河内塔)问题是源于印度的一个古老传说。大梵天创造世界时做了三根金刚石柱子,在一根柱子上从上往下按照由小到大的顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘重新摆放在另一根柱子上,顺序同样是从上往下由小到大摞列。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。下面是7层汉诺塔视频。

  把要求简述如下:abc三根柱子,a上有从上到下按从小到大顺序摞着一列圆盘,现要求把a柱子上所有的圆盘移动到c柱子上。移动规则如下:

  (1)三根柱子之间一次只能移动一个圆盘。

  (2)任何情况下,大圆盘不能放在小圆盘上面。

  解题思路:三个用途在三根柱子中是来回切换的。情况比较复杂,优先考虑用递归算法。根据递归算法的要求,首先看看碟子数量较少的情况能否解决。

  (1)当a柱上只有一个盘子时,直接将盘子移动到c柱上,如下图所示。

 

  (2)当a柱上有2个盘子时,操作步骤如图下图所示。

 

  (3)当a柱上有3个盘子时,操作步骤如图下图所示。

 

  显然,一到三个盘子的情况都是可以解决的。然后考虑n个盘子的情况,假设能把n-1个盘子移动到b柱上,把最大的盘子移到c柱上。这样又可以视作一个移动n-1个碟子的汉诺塔游戏,所以问题必然是可以用递归方法解决。

  算法分析:各个柱子在这过程中作用是变化的,为了操作的统一性,定义三个角色:源柱子、中间柱子和目标柱子。递归过程就是把步骤分解成下面的三个子步骤。

  (1)设源柱子上落有n个盘子,将前n1个盘子从源柱子移动到中间柱子上。

  (2)将最底下的最后一个盘子从源柱子移动到目标柱子上。

  (3)将中间柱子上的n1个盘子移动到目标柱子上。

  把(1)和(3)看做一次独立的n-1汉诺塔游戏,也就可以继续分解直到要移动1个盘子。

  柱子每次只能操作最上面的盘子,所以是栈,用栈来表示柱子。为了方便区分柱子,在Stack类中加入name属性。

 

  测试程序:

 

 

  打印结果:

 

  例题1:盘子是越多,搬动次数越多,那么64个盘子一共要搬动多少次呢?,,

  分析:因为要搬动底盘子,需要把它上的盘子全搬到中间柱子,然后把底盘子搬到目标柱子,然后把中间柱子的盘子全搬到目标柱子,所以,假如n个盘子的搬动次数是M次,那么n+1个盘子是2M+1次。那么4个盘子是:

((1*2+1)×2+1)×2+1=15次。

上面的程序正是搬动15次。

  程序代码:

 

  运行结果:

 

  练习题:本文开头的视频是Python海龟画图制作的,代码附录中,请把代码抄到Python文件后运行。

 

  学思营编程课堂基于蓝桥STEM86平台https://www.stem86.com,开展学编程三部曲:Scratch(三年级以前)>>>Python(四年级以后)>>>C++(七年级以后)教育实验活动,任何人可以免费参加,打开https://xuesiying.stem86.com网页注册进入课堂,也可关注本公众号留言。更多课程请打开学思营同步网站:http://www.5xstar.com/doc.html

 

参考资料:

1、《Python算法图解》何韬编著 清华大学出版社

 

附录:
import turtle as t  #导入海龟画图并把它重命名为t
import time         #导入时间模块


#初始化海龟
t.title("递归汉诺塔——学思营https://xuesiying.stem86.com") #设置标题
t.setup(500, 500)    #把窗口初始化
t.screensize(400, 400) #画布大小


#数据节点
class Node:
    def __init__(self, value):  #节点初始化
        self.value = value        #节点的值
        self.pre = None         #指向前一个节点的指针
        
#
class Stack:
    def __init__(self, name=None, location=None):  #栈初始化
        '''     
        @name为栈名
        @location为初始位置
        '''
        self.point = None      #定义栈顶指针
        self.length = 0        #栈中节点总数
        self.name = name      
        self.location =location
    def push(self, value): #向栈中压入数据方法
        node= Node( value)  #把压入栈中的数据包装成栈节点
        if self.point != None: #假如栈顶指针不为空
            node.pre = self.point #后压入栈的节点指向前一个节点
            self.point = node     #栈顶指针指向新压入栈的新节点
        else:                   #假如栈顶指针为空
            self.point = node     #栈顶指针指向栈中唯一的节点
        self.length += 1        #栈中总数加1
        add(self, node)       #加入海龟
    def pop(self):         #从栈中弹出数据方法
        if self.point != None:  #假如栈顶指针不为空
            node = self.point    #获得栈顶指针指向的节点
            self.point = node.pre #栈顶指针向前一个节点移动
            node.pre = None       #把栈顶节点的向前指针置为空
            self.length -= 1      #栈中节点总数减1
            div(self, node)       #减一节点
            return node.value     #返回弹出节点的值
        else:                     #如果栈内没有节点
            return None           #返回空
    def isNone(self):            #栈的判空方法
        if self.length>0:         #假如栈内节点总数大于0
            return False          #返回False
        else:                     #假如栈内节点总数等于0
            return True           #返回True

#加入海龟
def add(stack, node):
    stack.location = stack.location[0], stack.location[1]+25 #y坐标上一台阶
    node.value.st()
    node.value.setpos(stack.location) #移动海龟
    
#1海龟
def div(stack, node):
    stack.location = stack.location[0], stack.location[1]-25 #y坐标下一台阶
    node.value.ht()


#递归,层层分解
def move(n, s1, s2, s3):
    '''
    @n 塔的层数
    @s1 原塔
    @s2 中间塔
    @s3 目的塔
    '''
    if n != 1:
        move(n-1, s1, s3, s2)   #把底座上面所有盘子移动到中间柱子上
        move(1, s1, s2, s3)     #移动底座盘子到目标柱上
        move(n-1,s2, s1, s3)    #把中间柱子上的盘子移动到目标柱子上
    else:                       #当只剩一个盘子时
        #s1 -> s3
        s3.push(s1.pop())       #把盘子移动到目标柱子上
        print(s1.name, '=>', s3.name) #打印移动盘


t.up() #提笔

posA=(-100, -100)    #A塔位置
t.setpos(posA[0],posA[1])  #A塔写上标签A
t.write("A", align="center", font=("黑体", 14, "normal")) #居中14
a = Stack('A', posA) #声明1个栈——1根柱子起名叫A

posB=(0, -100)
t.setpos(posB[0],posB[1])
t.write("B", align="center", font=("黑体", 14, "normal"))
b = Stack('B', posB) #声明b柱子
posC=(100, -100)
t.setpos(posC[0],posC[1])
t.write("C", align="center", font=("黑体", 14, "normal"))
c = Stack('C', posC) #声明c柱子
        
t.shape("square") #采用正方形海龟
t.seth(90)        #海龟朝上
t.speed(3)        #海龟慢速移动


print("海龟画图初始化完成,3秒后构建7层汉诺塔。")
time.sleep(3)     #暂停3

temp = t.clone() #克隆海龟 最大的碟子,黑色
temp.shapesize(5, 0.5, 1) #横向5倍,同向0.5, 边框1
a.push(temp)     #加入A

temp = t.clone()
temp.shapesize(4.5, 0.5, 1)
temp.fillcolor("#FF0000")  #填充颜色
temp.pencolor("#FF0000")   #画笔颜色
a.push(temp)

temp = t.clone()
temp.shapesize(4, 0.5, 1)
temp.fillcolor("#FFFF00")
temp.pencolor("#FFFF00")
a.push(temp)

temp = t.clone()
temp.shapesize(3.5, 0.5, 1)
temp.fillcolor("#00FF00")
temp.pencolor("#00FF00")
a.push(temp)

temp = t.clone()
temp.shapesize(3, 0.5, 1)
temp.fillcolor("#00FFFF")
temp.pencolor("#00FFFF")
a.push(temp)

temp = t.clone()
temp.shapesize(2.5, 0.5, 1)
temp.fillcolor("#0000FF")
temp.pencolor("#0000FF")
a.push(temp)

temp = t.clone()
temp.shapesize(2, 0.5, 1)
temp.fillcolor("#FF00FF")
temp.pencolor("#FF00FF")
a.push(temp)

t.ht()  #隐藏海龟
print("7层汉诺塔初始化完成,3秒后移动。")
time.sleep(3)

n = a.length
move(n, a, b, c)