【数据结构与算法Python版学习笔记】图——骑士周游问题 深度优先搜索
骑士周游问题
概念
- 在一个国际象棋棋盘上, 一个棋子“马”(骑士) , 按照“马走日”的规则, 从一个格子出发, 要走遍所有棋盘格恰好一次。把一个这样的走棋序列称为一次“周游”
- 在8×8的国际象棋棋盘上, 合格的“周游”数量有1.305×1035这么多, 走棋过程中失败的周游就更多了
- 采用图搜索算法, 是解决骑士周游问题最容易理解和编程的方案之一
- 解决方案还是分为两步:
- 首先将合法走棋次序表示为一个图
- 采用图搜索算法搜寻一个长度为(行×列-1)的路径,路径上包含每个顶点恰一次
构建骑士周游图
- 为了用图表示骑士周游问题,我们将棋盘上的每一格表示为一个顶点,同时将骑士的每一次合理走法表示为一条边。
合法走棋位置函数
def genLegalMoves(x, y, bdSize):
newMove = []
# 马走日8个格子
moveOffsets = [
(-1, -2), (-1, 2), (-2, -1), (-2, 1),
(1, -2), (1, 2), (2, -1), (2, 1)
]
for i in moveOffsets:
newX = x+i[0]
newY = y+i[1]
if legalCoord(newX, bdSize) and legalCoord(newY, bdSize):
newMove.append((newX, newY))
return newMove
# 确保不会走出棋盘
def legalCoord(x, bdSize):
if x >= 0 and x < bdSize:
return True
else:
return False
构建走棋关系图
def knightGraph(bdSize):
ktGraph = Graph()
for row in range(bdSize):
for col in range(bdSize):
nodeId = posToNodeId(row, col, bdSize)
newPositions = genLegalMoves(row, col, bdSize)
for e in newPositions:
nid = posToNodeId(e[0], e[1].bdSize)
ktGraph.addEdge(nodeId, nid)
return ktGraph
def posToNodeId(row, col, bdSize):
return row*bdSize+col
8×8棋盘生成的图
具有336条边, 相比起全连接的4096条边, 仅8.2%, 还是稀疏图
骑士周游算法实现
简介
- 用于解决骑士周游问题的图搜索算法是
深度优先搜索(Depth First Search,简称DFS)
- 相比前述的广度优先搜索, 其逐层建立搜索树的特点
- 深度优先搜索是沿着树的单支尽量深入向下搜索
如果到无法继续的程度还未找到问题解就回溯上一层再搜索下一支 - DFS的两个实现算法
- 一个DFS算法用于解决骑士周游问题,其特点是每个顶点仅访问一次
- 另一个DFS算法更为通用,允许顶点被重复访问,可作为其它图算法的基础
深度优先搜索解决骑士周游的关键思路
- 如果沿着单支深入搜索到无法继续(所有合法移动都已经被走过了)时路径长度还没有达到预定值(8×8棋盘为63);那么就清除颜色标记,返回到上一层换一个分支继续深入搜索
- 引入一个栈来记录路径并实施返回上一层的回溯操作
代码实现
def knightTour(n, path, u, limit):
"""
n:层次;
path:路径;
u:当前顶点;
limit:搜索总深度
"""
u.setColor('gray')
# 当前点加入路径
path.append(u)
if n < limit:
# 对所有合法移动逐一深入
nbrList = list(u.getConnections())
i = 0
done = False
while i < len(nbrList) and not done:
if nbrList[i].getColor() == 'white': # 选择未经过的顶点深入
done = knightTour(n+1, path, nbrList[i], limit) # 层次+1,递归深入
i += 1
# 都无法完成总深度,回溯,试本层下一个顶点
if not done:
path.pop()
u.setColor('white')
else:
done = True
return done
骑士周游算法分析
- 上述算法的性能高度依赖于棋盘大小:
- 就5×5棋盘,约1.5秒可以得到一个周游路径
- 但8×8棋盘,则要半个小时以上才能得到一个解
- 目前实现的算法, 其复杂度为O(kn), 其中n是棋盘格数目
这是一个指数时间复杂度的算法!其搜索过程表现为一个层次为n的树
骑士周游算法改进
Warnsdorff算法
对nbrList的灵巧构造,以特定方式排列顶点访问次序可以使得8×8棋盘的周游路径搜索时间降低到秒级!
- 初始算法中nbrList, 直接以原始顺序来确定深度优先搜索的分支次序
- 新的算法, 仅修改了遍历下一格的次序
- 将u的合法移动目标棋盘格排序为:具有最少合法移动目标的格子优先搜索
优化代码
def orderByAvail(n):
resList = []
for v in n.getConnections():
if v.getColor() == 'white':
c = 0
for w in v.getConnections():
if w.getColor() == 'white':
c += 1
resList.append((c, v))
resList.sort(key=lambda x: x[0])
return [y[1] for y in resList]
启发式规则heuristic
- 采用先验的知识来改进算法性能的做法,称作为“启发式规则heuristic”
- 启发式规则经常用于人工智能领域;
- 可以有效地减小搜索范围、更快达到目标等等;
- 如棋类程序算法,会预先存入棋谱、布阵口诀、高手习惯等“启发式规则”,能够在最短时间内从海量的棋局落子点搜索树中定位最佳落子。
- 例如:黑白棋中的“金角银边”口诀,指导程序优先占边角位置等等
- 如棋类程序算法,会预先存入棋谱、布阵口诀、高手习惯等“启发式规则”,能够在最短时间内从海量的棋局落子点搜索树中定位最佳落子。
通用深度优先搜索
介绍
骑士周游问题是一种特殊的对图进行深度优先搜索
- 其目的是建立一个没有分支的最深的深度优先树
- 表现为一条线性的包含所有节点的退化树
一般的深度优先搜索目标是在图上进行尽量深的搜索, 连接尽量多的顶点, 必要时可以进行分支(创建了树)
- 有时候深度优先搜索会创建多棵树,称为
“深度优先森林”
- 有时候深度优先搜索会创建多棵树,称为
深度优先搜索同样要用到顶点的“前驱”属性, 来构建树或森林
另外要设置“
发现时间
”和“结束时间
”属性- 前者是在第几步访问到这个顶点(设置灰色)
- 后者是在第几步完成了此顶点探索(设置黑色)
这两个新属性对后面的图算法很重要
带有DFS算法的图实现为Graph的子类
- 顶点Vertex增加了成员Discovery及Finish
- 图Graph增加了成员time用于记录算法执行的步骤数目
通用的深度优先搜索算法代码
- BFS采用队列存储待访问顶点
- DFS则是通过递归调用,隐式使用了栈
class DFSGraph(Graph):
def __init__(self):
super.__init__()
self.time = 0
def dfs(self):
# 颜色初始化
for aVertex in self: # 遍历所有顶点
aVertex.setColor('white')
aVertex.setPred(-1)
# 如果还有未包括的顶点,则建森林
for aVertex in self:
if aVertex.getColor() == 'white':
self.adfvisit(aVertex)
def dfsvisit(self, startVertex):
startVertex.setColor('gray')
# 算法的步数
self.time += 1
startVertex.setDiscovery(self.time)
for nextVertex in startVertex.getConnections():
if nextVertex.getColor() == 'white':
nextVertex.setPred(startVertex)
# 深度优先递归访问
self.dfsvisit(nextVertex)
startVertex.setColor('black')
self.time+1
startVertex.setFinish(self.time)
算法分析
- DFS构建的树, 其顶点的“发现时间”和“结束时间”属性, 具有类似括号的性质
- 即一个顶点的“发现时间”总小于所有子顶点的“发现时间”
- 而“结束时间”则大于所有子顶点“结束时间”比子顶点更早被发现,更晚被结束探索
- DFS运行时间同样也包括了两方面:
- dfs函数中有两个循环,每个都是|V|次,所以是O(|V|)
- 而dfsvisit函数中的循环则是对当前顶点所连接的顶点进行,而且仅有在顶点为白色的情况下才进行递归调用,所以对每条边来说只会运行一步,所以是O(|E|)
- 加起来就是和BFS一样的O(|V|+|E|)
【数据结构与算法Python版学习笔记】图——骑士周游问题 深度优先搜索的更多相关文章
- 【数据结构与算法Python版学习笔记】引言
学习来源 北京大学-数据结构与算法Python版 目标 了解计算机科学.程序设计和问题解决的基本概念 计算机科学是对问题本身.问题的解决.以及问题求解过程中得出的解决方案的研究.面对一 个特定问题,计 ...
- 【数据结构与算法Python版学习笔记】目录索引
引言 算法分析 基本数据结构 概览 栈 stack 队列 Queue 双端队列 Deque 列表 List,链表实现 递归(Recursion) 定义及应用:分形树.谢尔宾斯基三角.汉诺塔.迷宫 优化 ...
- 【数据结构与算法Python版学习笔记】图——强连通分支
互联网 我们关注一下互联网相关的非常巨大图: 由主机通过网线(或无线)连接而形成的图: 以及由网页通过超链接连接而形成的图. 网页形成的图 以网页(URI作为id)为顶点,网页内包含的超链接作为边,可 ...
- 【数据结构与算法Python版学习笔记】图——拓扑排序 Topological Sort
概念 很多问题都可转化为图, 利用图算法解决 例如早餐吃薄煎饼的过程 制作松饼的难点在于知道先做哪一步.从图7-18可知,可以首先加热平底锅或者混合原材料.我们借助拓扑排序这种图算法来确定制作松饼的步 ...
- 【数据结构与算法Python版学习笔记】图——词梯问题 广度优先搜索 BFS
词梯Word Ladder问题 要求是相邻两个单词之间差异只能是1个字母,如FOOL变SAGE: FOOL >> POOL >> POLL >> POLE > ...
- 【数据结构与算法Python版学习笔记】图——最短路径问题、最小生成树
最短路径问题 概念 可以通过"traceroute"命令来跟踪信息传送的路径: traceroute www.lib.pku.edu.cn 可以将互联网路由器体系表示为一个带权边的 ...
- 【数据结构与算法Python版学习笔记】图——基本概念及相关术语
概念 图Graph是比树更为一般的结构, 也是由节点和边构成 实际上树是一种具有特殊性质的图 图可以用来表示现实世界中很多有意思的事物,包括道路系统.城市之间的航班.互联网的连接,甚至是计算机专业的一 ...
- 【数据结构与算法Python版学习笔记】查找与排序——散列、散列函数、区块链
散列 Hasing 前言 如果数据项之间是按照大小排好序的话,就可以利用二分查找来降低算法复杂度. 现在我们进一步来构造一个新的数据结构, 能使得查找算法的复杂度降到O(1), 这种概念称为" ...
- 【数据结构与算法Python版学习笔记】算法分析
什么是算法分析 算法是问题解决的通用的分步的指令的聚合 算法分析主要就是从计算资源的消耗的角度来评判和比较算法. 计算资源指标 存储空间或内存 执行时间 影响算法运行时间的其他因素 分为最好.最差和平 ...
随机推荐
- adb 常用命令大全(4)- 应用管理
查看应用列表 语法格式 adb shell pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER] ...
- uboot命令简介
uboot下的命令行 1.典型嵌入式linux系统启动过程: 嵌入式系统上电后先执行uboot.然后uboot负责初始化DDR,初始化Flash,然后将OS从Flash中读取到DDR中,然后启动OS( ...
- 10分钟学会VS NuGet包私有化部署
前言 我们之前实现了打包发布NuGet,但是发布后的引用是公有的,谁都可以访问,显然这种方式是不可取的. 命令版本:10分钟学会Visual Studio将自己创建的类库打包到NuGet进行引用(ne ...
- mybatis和hibernate区别
一.本质区别和应用场景
- Servlet3.0注解配置访问路径和urlParttern配置
一.Servlet用注解配置访问路径 二.IDEA的tomcat相关配置 其中,第一点的配置文件,直接在IDEA的可视化操作界面修改就可以改掉配置文件中内容: 三.urlParttern配置 其中,* ...
- C# Collection
数组与集合不同的适用范围: 数组:数组最适用于创建和使用固定数量的强类型化对象. 集合:集合提供更灵活的方式来使用对象组. 与数组不同,你使用的对象组随着应用程序更改的需要动态地放大和缩小. 对于某些 ...
- Vue3.x全家桶+vite+TS-构建Vue3基本框架
目录 一.搭建基础项目 1.vite创建项目 3.运行项目 2.环境变量设置介绍 vite配置多环境打包 二.配置Router 1.安装路由 2.配置路由 3.引入 三.配置Vuex 1.安装vuex ...
- python面向对象(封装,继承,多态)
python面向对象(封装,继承,多态) 学习完本篇,你将会深入掌握 如何封装一个优雅的借口 python是如何实现继承 python的多态 封装 含义: 1.把对象的属性和方法结合成一个独立的单位, ...
- 关于PHP数组Key的强制类型转换
PHP是弱类型语言,就像JavaScript一样,在定义变量时,不需要强制指定变量的类型.同时,PHP又有着强大的数组功能,数组的Key即可以是普通的数字类型下标,也可以是字符串类型的Hash键值,那 ...
- PHP多文件上传格式化
文件上传是所有web应用中最常见的功能,而PHP实现这一功能也非常的简单,只需要前端设置表单的 enctype 值为 multipart/form-data 之后,我们就可以通过 $_FILES 获得 ...