用python解决汉诺塔问题

2.6 汉诺塔问题

写出解5个环的汉诺塔问题的移动序列

代码

print('请输入汉诺塔的层数(为运算与展示考虑请尽量不要大于5)')
N = int(input())
global A, B, C, step
A = []
B = []
C = []
step = 0
for i in range(N):
    A.append(i)
A.reverse()


def pop(stack):  # 出栈操作。pop返回列表stack中的最后一个元素,并从原列表中删除该元素
    node = len(stack)
    if (node == 0):
        print('栈为空')  # 若栈中无元素,则报告栈为空的信息
        return
    else:
        pop = stack[node - 1]
        del stack[node - 1]
        return pop


def push(element, stack):  # 入栈操作。element为压入栈的元素,stack为栈,类型为列表,函数无返回值
    stack.append(element)
    return


def move(stack1, stack2):  # 汉诺塔的一次操作,将stack1的栈顶元素放至stack的栈顶
    if(len(stack1) == 0):
        return
    temp = pop(stack1)
    push(temp, stack2)
    return


def tower(s1, s2, s3, N=-1):  # 汉诺塔算法,借助s2,将s1的N层移动到s3
    global step
    if(N == -1):
        N = len(s1)

    if(N == 0):
        return
    elif(N == 1):  # 判断栈的深度,如果栈深为1,则一次简单移动即可;若大于1,则需要进行递归操作
        move(s1, s3)
        print('%-30s %-30s %-30s' % (A, B, C))  # 为了方便展示结果,输出语句做了调整
        step += 1
    else:
        tower(s1, s3, s2, N-1)  
        move(s1, s3)
        print('%-30s %-30s %-30s' % (A, B, C))
        step += 1
        tower(s2, s1, s3, N-1)
    return


tower(A, B, C)
print('所需步数为%d' % step)

输出结果

请输入汉诺塔的层数(为运算与展示考虑请尽量不要大于55
[4, 3, 2, 1]                   []                             [0]
[4, 3, 2]                      [1]                            [0]
[4, 3, 2]                      [1, 0]                         []
[4, 3]                         [1, 0]                         [2]
[4, 3, 0]                      [1]                            [2]
[4, 3, 0]                      []                             [2, 1]
[4, 3]                         []                             [2, 1, 0]
[4]                            [3]                            [2, 1, 0]
[4]                            [3, 0]                         [2, 1]
[4, 1]                         [3, 0]                         [2]
[4, 1, 0]                      [3]                            [2]
[4, 1, 0]                      [3, 2]                         []
[4, 1]                         [3, 2]                         [0]
[4]                            [3, 2, 1]                      [0]
[4]                            [3, 2, 1, 0]                   []
[]                             [3, 2, 1, 0]                   [4]
[0]                            [3, 2, 1]                      [4]
[0]                            [3, 2]                         [4, 1]
[]                             [3, 2]                         [4, 1, 0]
[2]                            [3]                            [4, 1, 0]
[2]                            [3, 0]                         [4, 1]
[2, 1]                         [3, 0]                         [4]
[2, 1, 0]                      [3]                            [4]
[2, 1, 0]                      []                             [4, 3]
[2, 1]                         []                             [4, 3, 0]
[2]                            [1]                            [4, 3, 0]
[2]                            [1, 0]                         [4, 3]
[]                             [1, 0]                         [4, 3, 2]
[0]                            [1]                            [4, 3, 2]
[0]                            []                             [4, 3, 2, 1]
[]                             []                             [4, 3, 2, 1, 0]
所需步数为31

解答过程

需要用到递归栈这一点,是一开始就可以想到的。因此coding的第一步就着手定义了出入栈的操作。然后定义移动栈顶元素的move操作,递归的tower函数。我以为接下来很快就可以完成,但是我在移动层数不为1时,如何写移动的过程碰到了问题。最开始我没有在tower函数中添加N这个参数,因此只能在传入tower的参数s1时,就直接将其定义为待移动的层。这意味着,每进入一层递归,我都需要将s1削去一层。但是我尝试s1[1:]时,得到的列表只是s1的复制,每次进行的操作并非在原始列表上进行。我陷入了一个死循环,百度了各种copy方法,还尝试过重新定义一个stack类。
最终我回去翻书,发现书本上已经给了非常完整的伪代码,因此我在tower函数中添加了N这个参数,这个问题就迎刃而解了。
但是这并不意味着代码已经完成了。在计算机内部,确实已经可以完成汉诺塔算法的操作,但是步数还未统计出来,操作的人也无法看到到具体的方法。汉诺塔算法tower在其内部的每一步move操作后状态都会发生改变,我们可以将其print出来以便观察。但是若是不以加工,汉诺塔的三个列表的显示顺序就会变幻无常。因此将汉诺塔的三个列表A,B,C和用于计算步数的step都定义为全局变量。

其他收获

在解答过程中很多的语法知识得到了新的理解

复制操作

有三种方法。分别是赋值、浅拷贝、深拷贝。执行这三种方法得到的新变量和原变量之间,可能会有或多或少的“关联”
赋值(=)可以说是最简单的复制,相当于为原来的变量起一个别名,对两个变量的操作完全一样,都会影响到对方。例如a = b
浅拷贝(如切片)是对原变量包含项的复制,如果用引用的方式修改其中一个变量,另外一个也会改变。例如a = [0, 1, 2]; b = a[:]或者b = copy(a)
深拷贝就是真正的复制,创建了一个新的变量,从此以后两者再不想干。例如b = copy.deepcopy(a)
之前学过一些c++,印象中里面的赋值语句是类似于python的深拷贝,是直接复制的。理解上还需要弄清楚
Q:为什么对列表的切片切片赋值时,原列表会受影响,而对切片做删除元素操作时,原列表不受影响?(可能是所谓的浅拷贝的原因吧)

return

return语句不跟后缀时,返回的值是None。在命令行不会显示出来,并且对其作布尔运算的结果是False。即bool(None) -> False

print

若要实现输出多行时,像表格一样的对齐效果,则需要借助占位符和对齐符。如该题代码中

print('%-30s %-30s %-30s' % (A, B, C))

其中的30就是占位符,表示为下一个输出的参数预留30个字符的空位,如果该参数占据了3个字符的空位,那么左边会留下27个字符的空位。30前面的符号-表示左对齐,若是改成正号+则是右对齐

对比和比较

自己折腾完以后在CSDN看了一点类似的文章,发现别人的代码异常的短。自己写的代码里面确实设置了很多不需要的东西,例如栈的操作pop和push并不需要定义。并且为了让结果有更好的可视化效果,代码的复杂程度又上了一个层次,然而最根本的问题却没有解决:给出移动序列。事实上,声明数组对其做实际操作,这种事情也根本不需要。递归函数中直接就可以输出移动序列
原始代码过于强调可视化效果,没有从更抽象的角度来思考问题的本质。就好像为本来追求速度的命令行系统包装了过于精细的图形界面,加了太多负担。今后需要加强抽象思维,从代码简洁、运行效率高的方面思考问题。
嘛, 不过对于我这样的萌新,这样的过程就当是熟悉语法啦。

你可能感兴趣的:(算法学(第三版),python)