递归的逻辑(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 = n9 # 初始化迷宫矩阵,所有方块未设置、有右墙、有上墙 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 random23 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 random2 import matplotlib.pyplot as plt34 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') # 右边框78 # 设置入口和出口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的设计图可以困住怪兽:
作者:我是8位的
出处:http://www.cnblogs.com/bigmonkey
本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!
扫描二维码关注公众号“我是8位的”
递归的逻辑(5)——米诺斯的迷宫相关推荐
- C++题目:米诺斯的迷宫
米诺斯的迷宫 题目背景 传说米诺斯王在克里特岛为米诺陶洛斯(Minotaur,"米诺斯的牛")修建了一个迷宫(Labyrinth).而你想找到米诺陶洛斯,然后杀掉他,从而成为英雄. ...
- 周伯通的空明拳,米诺斯的星尘傀儡线,SAP Kyma的Serverless
Jerry一直认为,金庸的<天龙八部>里的武学建模已经有点脱离传统武侠小说的范畴了,像已经走上玄幻道路的灵鹫宫"八荒六合唯我独尊功",以及杀伤力足够能被视为现代激光武器 ...
- 采用递归与栈结合的方式实现迷宫分析与走迷宫(python3)
一.场景 1. General presentation 2.Examples The file named maze_1.txt has the following contents. 二.题目分析 ...
- 递归的逻辑(1)——递归关系模型
查尔斯·巴贝奇是一名19世纪的英国发明家,也被说成是职业数学家.他曾经发明了差分机--一台能够按照设计者的意图,自动处理不同函数的计算过程的机器.这是一台硕大的.泛着微光的金属机器,包括数以千计加工精 ...
- 父亲节,来认识这些计算机领域的大佬们
导读:父亲节,华章妹想说说几位被称为"之父"的大佬. 01 信息论之父:克劳德·香农 克劳德·香农(Claude Elwood Shannon,1916年4月30日-2001年2月 ...
- 欧洲5000年文明史,到底是怎么来的?
近代以来,随着欧洲逐渐崛起之后,便开始寻找他们"辉煌的历史",由此证明欧洲文明的优越性,与领先其他文明.成为世界中心的深刻历史原因.十九世纪至二十世纪,经过百余年的考古探索,欧洲学 ...
- MIT的《深度学习》精读(2)
看完了"皮格马利翁"和"伽拉泰亚(Galatea)"的故事,已经很让人感动,接着下来,又来看看"代达罗斯"和"塔罗斯(Talos) ...
- 如何交互可视化你的卡片式笔记网络?
王树义 读完需要 13 分钟 速读仅需 5 分钟 会当凌绝顶,一览众山小. 1 痛点 在<如何高效实践卡片式写作?>和<如何用卡片法写论文?>两篇文章中,我为你详细介绍了卢曼的 ...
- java递归老鼠走迷宫_老鼠走迷宫----------递归问题
老鼠走迷宫是一个典型的递归的问题,写几个这样的题才可以充分理解递归的过程. 写递归的过程有几点需要注意: (1)递归结束的条件 (2)递归过程的脉络,即逻辑要清晰. / // // 在迷宫中显示老鼠能 ...
最新文章
- PyTorch的十七个损失函数
- 人脸识别技术及其应用领域
- 前沿|Google AI提新型神经网络,对神经元进行高精度自动重建
- vs2017开发php,C#编写的可供PHP调用的com dll(Visual studio 2017)
- SVN钩子--hook
- 如何假装自己读懂了《时间简史》
- python 获取向上两级路径_Python学习第171课--相对路径和绝对路径
- jQuery的实现原理和核心
- OpenCV图像、矩阵、数组介绍
- 【小白学前端】化腐朽为神奇-Bootstrap实现表单美化(day02-6)
- ApacheCN 活动汇总 2019.8.9
- 老司机必备-安卓+PC磁链下载播放工具
- 2018年最值得投资的十大行业版图
- GetLastErr返回值ErrCode的宏定义以及含义
- 股票爆仓应该具体怎么办呢?股票爆仓常见的危险是什么?
- 除了支付宝,微信也能查询账单了!再也不担心钱花哪去了!
- Druid在有赞的实践
- 版本控制Git 黑马尚硅谷
- GIS系列(三)几种互联网地图服务背后的解读(WMS,WFS,WMTS,TMS)
- dll.a和lib 引用MinGW生成的dll.a后出现的问题