• 递归的逻辑(5)——米诺斯的迷宫


      米诺斯迷宫的传说来源于克里特神话,在希腊神话中也有大量的描述,号称世界四大迷宫之一。

      米诺斯是宙斯和欧罗巴的儿子,因智慧和公正而闻名,死后成为了冥国的判官。由于米诺斯得罪了海神波塞冬,波塞冬便以神力使米诺斯的妻子帕西法厄爱上了一头公牛,生下了一个牛首人身的怪物米诺陶洛斯。这个半人半牛的怪物不吃其他食物,只吃人肉,因此米诺斯把他关进一座迷宫中,令它无法危害人间。

      后来雅典人杀死了米诺斯的一个儿子,为了复仇,米诺斯恳求宙斯的帮助。宙斯给雅典带来了瘟疫,为了阻止瘟疫的流行,雅典从必须每年选送七对童男童女去供奉怪物米诺陶洛斯。

      当雅典第三次纳贡时,王子忒修斯自愿充当祭品,以便伺机杀掉怪物,为民除害。当勇敢的王子离开王宫时,他对自己的父亲说,如果他胜利了,船返航时便会挂上白帆,反之则还是黑帆。忒修斯到了米诺斯王宫,公主艾丽阿德涅对他一见钟情,并送他一团线球和一柄魔剑,叫他将线头系在入口处,放线进入迷宫。忒修斯在迷宫深处找到了米诺陶洛斯,经过一场殊死搏斗,终于将其杀死。

      忒修斯带着深爱他的艾丽阿德涅公主返回雅典,却在途中把她抛在一座孤岛上。由于他这一背信弃义的行为,他遭到了惩罚——胜利的喜悦冲昏了他的头脑,他居然忘记更换船上的黑帆!结果,站在海边遥望他归来的父亲看到那黑帆之后,认为儿子死掉了,便悲痛地投海而死。

      似乎我很小的时候就听过这个故事,随着时间的流逝,故事的梗概早已忘却,但那个神奇的迷宫却至今都记忆犹新。虽然不清楚当时的迷宫是怎样设计的,但是我们可以通过递归的方法让米诺斯的迷宫重现人间。

     1 # 迷宫矩阵
     2 maze = [
     3     [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
     4     [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
     5     [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1],
     6     [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1],
     7     [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1],
     8     [1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1],
     9     [1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1],
    10     [1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1],
    11     [1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1],
    12     [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1],
    13     [1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1],
    14     [1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1],
    15     [1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
    16     [1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
    17     [1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
    18     [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    19 ]
    20 def paint(maze):
    21     ''' 打印迷宫 '''
    22     for a in maze:
    23         for i in a:
    24             print('%4d' % i, end='')
    25         print()
    26
    27 if __name__ == '__main__':
    28     paint(maze)

      在矩阵中,用0表示通道,1表示墙壁,忒修斯王子可以在0之间任意穿行,矩阵迷宫的打印结果:

    迷宫的数据结构

      虽然可以用0和1绘制出一个迷宫,但仍然属于手动编辑,我们的目标是寄希望于计算机,自动生成并绘制一个大型的迷宫:

      迷宫中有很多墙壁,再用0和1组成的简单矩阵就不合适了。如果将迷宫矩阵的每一个位置看作一个方块,则方块的上、下、左、右都可能有墙壁存在,这就需要对每个位置记录四面墙壁的信息:

      实际上没那么复杂,只要记录上墙和右墙就可以了,至于下墙和左墙,完全可以由相邻方块的上墙和右墙代替:

      当然,最后还要在四周套上一层边框:

      在生成迷宫时,每一个方块都需要记录三种信息:是否已经被设置、是否有右墙、是否有上墙。一个较为“面向对象”的方法是将方块信息设计成一个类结构,用三个布尔型属性来记录信息,但是这样做性价比并不高,一种更简单且高效的方式是用一个3位的二进制数来表示:

      矩阵中所有元素的初始值都设置为011,也就是方块未设置、有右墙和上墙;如果已经设置了某个方块,那么第3位被置为1,如此一来,每个方块可能会有5种状态:

      使用下面的代码设置一个8×8迷宫矩阵的初始状态:

     1 # 迷宫矩阵
     2 class MinosMaze:
     3     maze = []            # 迷宫矩阵
     4     n = 0                # 矩阵维度
     5     init_status = 0b011  # 初始状态,有上墙和右墙
     6     def __init__(self, n: int):
     7         ''' 初始化一个 n 维的迷宫 '''
     8         self.n = n
     9         # 初始化迷宫矩阵,所有方块未设置、有右墙、有上墙
    10         self.maze = [([self.init_status] * n) for i in  range(n)]
    11
    12     def patin_maze(self):
    13         for a in self.maze:
    14             for i in a:
    15                 print('%4d' % i, end='')
    16             print()
    17
    18 if __name__ == '__main__':
    19     m = MinosMaze(8)
    20     m.patin_maze()

      我们使用拆墙法自动生成迷宫,这需要遍历迷宫矩阵中的每一个方格,设置是否拆除右墙或上墙。用递归的方法随机遍历上下左右四个方向,直到所有方向全部遍历完为止:

      四个的拆墙过程如下:

      1. 向上遍历,需要拆除当前方格的上墙;

      2. 向下遍历,需要拆除下侧方格的上墙;

      3. 向左遍历,需要拆除左侧方格的右墙;

      4. 向右遍历,需要拆除当前单元格的右墙。

    1 class MinosMaze:
    2     ……
    3     def remove_wall(self, i, j, side):
    4         ''' 拆掉maze[i][j] 的上墙或右墙 '''
    5         if side == 'U':
    6             self.maze[i][j] &= 0b110  # 拆掉上墙
    7         elif side == 'R':
    8             self.maze[i][j] &= 0b101  # 拆掉右墙

    自动生成迷宫

      通过递归的方式遍历方格,迷宫矩阵的方格会逐一被设置:

     1 import random
     2
     3 class MinosMaze:
     4     ...
     5     def create(self):
     6         ''' 自动创建迷宫 '''
     7         def auto_create(i, j):
     8             self.maze[i][j] |= 0b100    # maze[i][j] 已经被设置过
     9             # 当self.maze[i][j]的上下左右四个方向都是初始状态时,开始拆墙操作
    10             while (i - 1 >= 0 and self.maze[i - 1][j] == self.init_status) 
    11                     or (i + 1 < self.n and self.maze[i + 1][j] == self.init_status) 
    12                     or (j - 1 >= 0 and self.maze[i][j - 1] == self.init_status) 
    13                     or (j + 1 < self.n and self.maze[i][j + 1] == self.init_status):
    14                 side = random.choice(['U', 'D', 'L', 'R'])   # 随机方向
    15                 # 能够向↑走
    16                 if side == 'U' and i - 1 >= 0 and self.maze[i - 1][j] == self.init_status:
    17                     self.remove_wall(i, j, 'U')     # 拆除当前方格的上墙
    18                     auto_create(i - 1, j)           # 向↑走
    19                 # 能够向↓走
    20                 elif side == 'D' and i + 1 < self.n and self.maze[i + 1][j] == self.init_status:
    21                     self.remove_wall(i + 1, j, 'U') # 拆除下侧方格的上墙
    22                     auto_create(i + 1, j)           # 向↓走
    23                 # 能够向←走
    24                 elif side == 'L' and j - 1 >= 0 and self.maze[i][j - 1] == self.init_status:
    25                     self.remove_wall(i, j - 1, 'R') # 拆除左侧方格的右墙
    26                     auto_create(i, j - 1)           # 向←走
    27                 # 能够向→走
    28                 elif side == 'R' and j + 1 < self.n and self.maze[i][j + 1] == self.init_status:
    29                     self.remove_wall(i, j, 'R')     # 拆除当前单元格的右墙
    30                     auto_create(i, j + 1)           # 向→走
    31         auto_create(0, 0)   # 从入口位置开始遍历
    32
    33     def patin_maze(self):
    34         ''' 打印迷宫数组 '''
    35         for a in self.maze:
    36             for i in a:
    37                 print('%4d' % i, end='')
    38             print()
    39
    40 if __name__ == '__main__':
    41     m = MinosMaze(8)
    42     m.create()
    43     m.patin_maze()

      程序构造了一个8×8的迷宫,一种可能的结果是:

      矩阵元素的打印的结果是十进制整数,它和二进制的对应关系:

    画出迷宫

      绘制迷宫的方法很简单,只需在坐标轴中画出每个方格的墙壁就好了:

     1 import random
     2 import matplotlib.pyplot as plt
     3
     4 class MinosMaze:
     5     ……
     6     def paint(self):
     7         # 绘制迷宫内部
     8         for i in range(self.n):
     9             for j in range(self.n):
    10                 # 有右墙
    11                 if self.maze[i][j] & 0b010 == 0b010:
    12                     # 右墙的坐标
    13                     r_x, r_y = [j + 1, j + 1], [self.n - i, self.n - i - 1]
    14                     plt.plot(r_x, r_y, color='black')
    15                 # 有上墙
    16                 if self.maze[i][j] & 0b001 == 0b001:
    17                     # 上墙的坐标
    18                     u_x, u_y = [j, j + 1], [self.n - i, self.n - i]
    19                     plt.plot(u_x, u_y, color='black')
    20
    21         plt.axis('equal')
    22         ax = plt.gca()
    23         ax.spines['top'].set_visible(False)
    24         ax.spines['right'].set_visible(False)
    25         plt.show()

      看起来不那么像迷宫,这是由于没有添加边框,因此还需要在paint()方法中加上最后的完善工作:

     1 def paint(self):
     2     ……
     3     plt.plot([0, self.n], [self.n, self.n], color='black')   # 上边框
     4     plt.plot([0, self.n], [0, 0], color='black')             # 下边框
     5     plt.plot([0, 0], [0, self.n], color='black')             # 左边框
     6     plt.plot([self.n, self.n], [0, self.n], color='black')  # 右边框
     7
     8     # 设置入口和出口
     9     entrance, exit = ([0, 0], [self.n, self.n - 1]), ([self.n, self.n], [0, 1])
    10     plt.plot(entrance[0], entrance[1], color='white')
    11     plt.plot(exit[0], exit[1], color='white')
    12
    13     plt.axis('equal')
    14     ax = plt.gca()
    15     ax.spines['top'].set_visible(False)
    16     ax.spines['right'].set_visible(False)
    17     plt.show()

      出口的位置在迷宫的右下角,由于创建迷宫时遍历了所有方格,因此出口方格一定是从它上侧或左侧的方格遍历而来的,这意味着它一定没有上墙或左墙,拆掉它的右边框一定能够成为出口。现在可以终于可以绘制出一个完整的迷宫了:

      米诺斯的迷宫复杂的多,也许一个32×32的设计图可以困住怪兽:

      以下是完整的代码:

      1 import random
      2 import matplotlib.pyplot as plt
      3 
      4 # 迷宫矩阵
      5 class MinosMaze:
      6     ''' 米诺斯迷宫
      7         Attributes:
      8             maze:           迷宫矩阵
      9             n:              矩阵维度
     10             init_status:    方格的初始状态
     11         '''
     12     maze = []
     13     n = 0
     14     init_status = 0b011  # 初始状态,有上墙和右墙
     15 
     16     def __init__(self, n: int):
     17         ''' 初始化一个 n 维的迷宫 '''
     18         self.n = n
     19         # 初始化迷宫矩阵,所有方块未设置、有右墙、有上墙
     20         self.maze = [([self.init_status] * n) for i in  range(n)]
     21 
     22     def remove_wall(self, i, j, side):
     23         ''' 拆掉maze[i][j] 的上墙或右墙 '''
     24         if side == 'U':
     25             # 拆掉上墙
     26             self.maze[i][j] &= 0b110
     27         elif side == 'R':
     28             # 拆掉右墙
     29             self.maze[i][j] &= 0b101
     30 
     31     def create(self):
     32         ''' 自动创建迷宫 '''
     33         def auto_create(i, j):
     34             # maze[i][j] 已经被设置过
     35             self.maze[i][j] |= 0b100
     36 
     37             # 当self.maze[i][j]的上下左右四个方向都是初始状态时,开始拆墙操作
     38             while (i - 1 >= 0 and self.maze[i - 1][j] == self.init_status) 
     39                     or  (i + 1 < self.n and self.maze[i + 1][j] == self.init_status) 
     40                     or (j - 1 >= 0 and self.maze[i][j - 1] == self.init_status) 
     41                     or (j + 1 < self.n and self.maze[i][j + 1] == self.init_status):
     42                 # 随机方向
     43                 side = random.choice(['U', 'D', 'L', 'R'])
     44                 # 能够向↑走
     45                 if side == 'U' and i - 1 >= 0 and self.maze[i - 1][j] == self.init_status:
     46                     # 拆除当前方格的上墙
     47                     self.remove_wall(i , j, 'U')
     48                     # 向↑走
     49                     auto_create(i - 1, j)
     50                 # 能够向↓走
     51                 elif side == 'D' and i + 1 < self.n and self.maze[i + 1][j] == self.init_status:
     52                     # 拆除下侧方格的上墙
     53                     self.remove_wall(i + 1 , j, 'U')
     54                     # 向↓走
     55                     auto_create(i + 1, j)
     56                 # 能够向←走
     57                 elif side == 'L' and j - 1 >= 0 and self.maze[i][j - 1] == self.init_status:
     58                     # 拆除左侧方格的右墙
     59                     self.remove_wall(i, j - 1, 'R')
     60                     # 向←走
     61                     auto_create(i, j - 1)
     62                 # 能够向→走
     63                 elif side == 'R' and j + 1 < self.n and self.maze[i][j + 1] == self.init_status:
     64                     # 拆除当前单元格的右墙
     65                     self.remove_wall(i, j, 'R')
     66                     # 向→走
     67                     auto_create(i, j + 1)
     68         # 从入口位置开始遍历
     69         auto_create(0, 0)
     70 
     71     def patin_maze(self):
     72         for a in self.maze:
     73             for i in a:
     74                 print('%4d' % i, end='')
     75             print()
     76 
     77     def paint(self):
     78         # 绘制迷宫内部
     79         for i in range(self.n):
     80             for j in range(self.n):
     81                 # 有右墙
     82                 if self.maze[i][j] & 0b010 == 0b010:
     83                     # 右墙的坐标
     84                     r_x, r_y = [j + 1, j + 1], [self.n - i, self.n - i - 1]
     85                     plt.plot(r_x, r_y, color='black')
     86                 # 有上墙
     87                 if self.maze[i][j] & 0b001 == 0b001:
     88                     # 上墙的坐标
     89                     u_x, u_y = [j, j + 1], [self.n - i, self.n - i]
     90                     plt.plot(u_x, u_y, color='black')
     91 
     92         # 上边框
     93         plt.plot([0, self.n], [self.n, self.n], color='black')
     94         # 下边框
     95         plt.plot([0, self.n], [0, 0], color='black')
     96         # 左边框
     97         plt.plot([0, 0], [0, self.n], color='black')
     98         # 右边框
     99         plt.plot([self.n, self.n], [0, self.n], color='black')
    100 
    101         # 设置入口和出口
    102         entrance, exit = ([0, 0], [self.n, self. n - 1]), ([self.n, self.n], [0, 1])
    103         plt.plot(entrance[0], entrance[1], color='white')
    104         plt.plot(exit[0], exit[1], color='white')
    105 
    106         plt.axis('equal')
    107         ax = plt.gca()
    108         ax.spines['top'].set_visible(False)
    109         ax.spines['right'].set_visible(False)
    110         plt.show()
    111 
    112 if __name__ == '__main__':
    113     # m = MinosMaze(8)
    114     # m = MinosMaze(16)
    115     m = MinosMaze(32)
    116     m.create()
    117     m.patin_maze()
    118     m.paint()

       作者:我是8位的

      出处:http://www.cnblogs.com/bigmonkey

      本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途! 

      扫描二维码关注公众号“我是8位的”

  • 相关阅读:
    git提交本地代码到远程服务器
    报错 D:Program Files odejs ode_cache\_logs2019-05-07T07_07_30_992Z-debug.log
    vue项目中使用插件将字符串装化为格式化的json数据(可伸缩)
    odoo官方文档第二章 Data Files
    odoo官方文档第一章 ORM
    odoo模块的创建 openacademy学习笔记
    mysql存储过程的学习(二)
    mysql存储过程的学习(一)
    linux 进入mysql的常用命令(转)
    Dubbo入门学习(转)
  • 原文地址:https://www.cnblogs.com/bigmonkey/p/10404106.html
Copyright © 2020-2023  润新知