数据结构与算法——30. 广度、深度优先搜索及骑士周游问题
文章目录
- 一、广度优先搜索(Breadth First Search,BFS)
- 1. BFS算法过程
- 2. python实现
- 3. 算法分析
- 二、图的应用:骑士周游问题
- 1. 构建骑士周游图
- 2. 骑士周游问题算法实现
- 3. 骑士周游问题算法分析与改进
- (1)算法分析
- (2)算法改进
- 4. 启发式规则(heuristic)
- 三、深度优先搜索(Depth First Search,DFS)
- 1. python代码实现
- 2. 算法分析
一、广度优先搜索(Breadth First Search,BFS)
在前面的词梯问题中,在单词关系图建立完成以后,需要继续在图中寻找词梯问题的最短序列,这就需要用到“广度优先搜索(Breadth First Search,BFS)”。BFS是搜索图的最简单算法之一,也是其它一些重要的图算法的基础。
给定图G,以及开始搜索的起始顶点s。BFS搜索所有从s可到达顶点的边。而且在达到更远的距离k+1的顶点之前,BFS会找到全部距离为k的顶点。(看不懂没关系,看完后面的举例就明白了)
可以想象为以s为根,构建一棵树的过程,从顶部向下逐步增加层次。广度优先搜索能保证在增加层次之前,添加了所有兄弟节点到树中。
1. BFS算法过程
我们从fool开始搜索,从fool可以到达的顶点有foul、foil、cool、pool,完成第一轮,距离为1。然后再依次以这四个顶点为起点,寻找下一步可以到达的顶点fail、poll,完成第2轮,距离等于2。然后是第三轮……。如此反复,直到抵达目标sage为止。
但有些顶点之间的距离是不确定的,比如fool到pool,可以直接到达,距离为1;也可以通过cool到达,距离为2。
为了跟踪顶点的加入过程,并避免重复顶点,要为顶点增加3个属性:
- 距离(distance):从起始顶点到此顶点路径长度;
- 前驱顶点(predecessor):可反向追溯到起点;
- 颜色(color):标识了此顶点是尚未发现(白色)、已经发现(灰色)、还是已经完成探索(黑色)。
还需要用一个队列Queue来对已发现的顶点进行排列,已经发现的放到队首,完成探索的放到队尾。以此决定下一个要探索的顶点(队首顶点)。
算法过程:
从起始顶点s开始,作为刚发现的顶点,标注为灰色,距离为0,前驱为None。将s加入队列,接下来是个循环迭代过程:
- 从队首取出一个顶点作为当前顶点;
- 遍历当前顶点的邻接顶点;
- 如果是尚未发现的白色顶点,则将其颜色改为灰色(已发现),距离增加1,前驱顶点为当前顶点,加入到队列中;
- 遍历完成后,将当前顶点设置为黑色(已探索过),循环回到步骤1的队首取当前顶点。
2. python实现
def bfs(g,start): # 参数:图,起点# 起点的距离设置为1start.setDistance(0)# 起点的前驱设置为Nonestart.setPred(None)vertQueue = Queue()# 将起点放入队首vertQueue.enqueue(start)while (vertQueue.size() > 0):# 取出队列的队首元素作为当前顶点currentVert = vertQueue.dequeue()# 遍历当前顶点的邻接顶点for nbr in currentVert.getConnections():if (nbr.getColor() == 'white'):nbr.setColor('gray')nbr.setDistance(currentVert.getDistance() + 1)nbr.setPred(currentVert)vertQueue.enqueue(nbr)# 当前顶点探索完成,设置为黑色currentVert.setColor('black')
在以FOOL为起始顶点,遍历了所有顶点,并为每个顶点着色、赋距离和前驱之后。就可以通过一个回途追溯函数来确定FOOL到任何单词顶点的最短词梯!
def traverse(y): # y是目标单词x = ywhile (x.getPred()):print(x.getId())x = x.getPred()print(x.getId())traverse(g.getVertex('sage'))
3. 算法分析
BFS算法主体是两个循环的嵌套:
while循环对每个顶点访问一次,所以是O(∣V∣)O(|V|)O(∣V∣),V是顶点数量;
而嵌套在while中的for,由于每条边只有在其起始顶点u出队的时候才会被检查一次,而每个顶点最多出队1次,所以边最多被检查1次,一共是O(∣E∣)O(|E|)O(∣E∣),E是边的数量。
综合起来BFS的时间复杂度为O(∣V∣+∣E∣)O(|V|+|E|)O(∣V∣+∣E∣)。
建立BFS树之后,回溯顶点到起始顶点的过程,最多为O(∣V∣)O(|V|)O(∣V∣)。创建单词关系图也需要时间,最多为O(∣V∣2)O(|V|^2)O(∣V∣2)。
二、图的应用:骑士周游问题
在一个国际象棋棋盘上,一个棋子“马”(骑士),按照“马走日”的规则,从一
个格子出发,要走遍所有棋盘格恰好一次。把一个这样的走棋序列称为一次“周游”。
在8×8的国际象棋棋盘上,合格的“周游”数量有1.305×10351.305\times 10^{35}1.305×1035这么多,走棋过程中失败的周游就更多了。
采用图搜索算法,是解决骑士周游问题最容易理解和编程的方案之一,解决方案还是分为两步:
- 首先将合法走棋次序表示为一个图;
- 采用图搜索算法搜寻一个长度为(行×列−1行\times 列-1行×列−1)的路径,路径上包含每个顶点恰一次。
1. 构建骑士周游图
将棋盘和走棋步骤构建为图的思路:
- 将棋盘格作为顶点;
- 按照“马走日”规则的走棋步骤作为连接边;
- 建立每一个棋盘格的所有合法走棋步骤能够到达的棋盘格关系图。
比如下面的“马”,它的下一步合法移动可以表示为右边的图:
它可以走的合法格子最多8个,如果“马”最初在一个靠边的位置,那么就能走的格子就少于8个。
合法走棋位置函数:
def genLegalMoves(x,y,bdSize):newMoves = []"""“马”走日,可以前往的格子最多有8个通过对“马”的当前位置(x、y)进行加减,得到合法的移动位置"""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):newMoves.append((newX,newY))return newMovesdef legalCoord(x,bdSize):# 确保不会走出棋盘if x >= 0 and x < bdSize:return Trueelse: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 ktGraphdef posToNodeId(row, column, board_size):return (row * board_size) + column
骑士周游图:
8×8棋盘生成的图,具有336条边,相比起全连接的4096条边,仅8.2%,还是稀疏图。
2. 骑士周游问题算法实现
用于解决骑士周游问题的图搜索算法是深度优先搜索(Depth First Search)。
相比前述的广度优先搜索,其逐层建立搜索树的特点。深度优先搜索是沿着树的单支尽量深入向下搜索,如果到无法继续的程度还未找到问题解,就回溯上一层再搜索下一支。
后面会介绍DFS的两个实现算法:
一个DFS算法用于解决骑士周游问题,其特点是每个顶点仅访问一次;
另一个DFS算法更为通用,允许顶点被重复访问,可作为其它图算法的基础。
深度优先搜索解决骑士周游的关键思路:
- 如果沿着单支深入搜索到无法继续(所有合法移动都已经被走过了)时,路径长度还没有达到预定值(8×8棋盘为63),那么就清除颜色标记,返回到上一层。换一个分支继续深入搜索。
- 引入一个栈来记录路径,并实施返回上一层的回溯操作。
python代码实现:
def knightTour(n,path,u,limit):""":param n: 层次,当前走了多少步:param path: 路径,走过的格子:param u: 当前顶点:param limit: 搜索总深度限制,比如8*8=63"""u.setColor('gray')# 当前顶点加入路径列表末尾(这个列表用来模拟栈)path.append(u)# 搜索深度没有到达限制时,继续深入搜索if n < limit:# 对所有合法移动逐一深入nbrList = list(u.getConnections())i = 0done = Falsewhile i < len(nbrList) and not done:# 选择白色未经过的顶点深入if nbrList[i].getColor() == 'white':# 层次加1,递归深入done = knightTour(n+1, path, nbrList[i], limit)i = i + 1# 都无法完成总深度,回溯,试本层下一个顶点if not done: path.pop()u.setColor('white')else:done = Truereturn done
下图就是骑士周游问题的一个解:
3. 骑士周游问题算法分析与改进
(1)算法分析
上述算法的性能高度依赖于棋盘大小:
就5×55\times 55×5棋盘而言,约1.5秒可以得到一个周游路径。但8×88\times 88×8棋盘,则要半个小时以上才能得到一个解。
目前实现的算法,其复杂度为O(kn)O(k^n)O(kn),其中n是棋盘格数目。这是一个指数时间复杂度的算法!其搜索过程表现为一个层次为n的树。
(2)算法改进
对nbrList
的灵巧构造,以特定方式排列顶点访问次序。可以使得8×88\times 88×8棋盘的周游路径搜索时间降低到秒级!这个改进算法被特别以发明者名字命名:Warnsdorff算法。
初始算法中nbrList
,直接以原始顺序来确定深度优先搜索的分支次序。新的算法,仅修改了遍历下一格的次序。将u
的合法移动目标棋盘格排序为:具有最少合法移动目标的格子优先搜索:
def orderByAvail(n):resList = []for v in n.getConnections():if v.getColor() == 'white':c = 0for w in v.getConnections():if w.getColor() == 'white':c = c + 1resList.append((c,v))resList.sort(key=lambda x: x[0])return [y[1] for y in resList]
4. 启发式规则(heuristic)
采用先验的知识来改进算法性能的做法,称作为“启发式规则”。
启发式规则经常用于人工智能领域;可以有效地减小搜索范围、更快达到目标等等;
如棋类程序算法,会预先存入棋谱、布阵口诀、高手习惯等“启发式规则”,能够在最短时间内,从海量的棋局落子点搜索树中定位最佳落子。例如:黑白棋中的“金角银边”口诀,指导程序优先占边角位置等等
三、深度优先搜索(Depth First Search,DFS)
骑士周游问题是一种特殊的对图进行深度优先搜索,其目的是建立一个没有分支的最深的深度优先树,表现为一条线性的包含所有节点的退化树,从0开始直接到63,没有别的岔路。
通用的深度优先搜索目标是在图上进行尽量深的搜索,连接尽量多的顶点,必要时可以进行分支(创建了树)。有时候深度优先搜索会创建多棵树,称为“深度优先森林”。
深度优先搜索同样要用到顶点的“前驱”属性,来构建树或森林。另外要设置“发现时间”和“结束时间”属性:
- 前者是在第几步访问到这个顶点(设置灰色)
- 后者是在第几步完成了此顶点探索(设置黑色)
这两个新属性对后面的图算法很重要。
带有DFS算法的图实现为Graph的子类,顶点Vertex增加了成员Discovery及Finish,图Graph增加了成员time用于记录算法执行的步骤数目。
1. python代码实现
class DFSGraph(Graph):def __init__(self):super().__init__()self.time = 0def dfs(self):for aVertex in self:# 所有顶点初始化为白色aVertex.setColor('white')# 建立一棵树aVertex.setPred(-1)# 如果还有未包括的顶点,则建森林for aVertex in self:if aVertex.getColor() == 'white':self.dfsvisit(aVertex)def dfsvisit(self,startVertex):startVertex.setColor('gray')self.time += 1startVertex.setDiscovery(self.time)for nextVertex in startVertex.getConnections():if nextVertex.getColor() == 'white':nextVertex.setPred(startVertex)# 深度优先递归访问self.dfsvisit(nextVertex)startVertex.setColor('black')self.time += 1startVertex.setFinish(self.time)
图解示例,灰色的为“刚刚发现或开始探索”,白色的为“未发现”,黑色的为“探索完成”:
2. 算法分析
DFS构建的树,其顶点的“发现时间”和“结束时间”属性,具有类似括号的性质:
- 一个顶点的“发现时间”总小于所有子顶点的“发现时间”;
- “结束时间”则大于所有子顶点“结束时间”;
- 比子顶点更早被发现,更晚被结束探索。
比如,上图中B的“发现时间”为2,小于C、D、E、F的“发现时间”;“结束时间”为11,大于C、D、E、F的“结束时间”。
DFS运行时间同样也包括了两方面:
dfs
函数中有两个循环,每个都是∣V∣|V|∣V∣次,所以是O(∣V∣)O(|V|)O(∣V∣);dfsvisit
函数中的循环则是对当前顶点所连接的顶点进行,而且仅有在顶点为白色的情况下才进行递归调用,所以对每条边来说只会运行一步,所以是O(∣E∣)O(|E|)O(∣E∣);- 加起来就是和BFS一样的O(∣V∣+∣E∣)O(|V|+|E|)O(∣V∣+∣E∣)。
数据结构与算法——30. 广度、深度优先搜索及骑士周游问题相关推荐
- 【数据结构与算法】2.深度优先搜索DFS、广度优先搜索BFS
原文链接:https://blog.csdn.net/qq_41681241/article/details/81432634 总结 一般来说,广搜常用于找单一的最短路线,或者是规模小的路径搜索,它的 ...
- 啊哈算法之简单深度优先搜索案例
简述 本算法摘选自啊哈磊所著的<啊哈!算法>第四章第一节的内容--深度优先搜索(DFS).其实这个名词以前听说过很多次,但是就是没有了解过这是什么东西,感觉很深奥离自己还很远,而且目前遇到 ...
- 【算法入门】深度优先搜索(DFS)
深度优先搜索(DFS) [算法入门] 郭志伟@SYSU:raphealguo(at)qq.com 2012/05/12 1.前言 深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍 ...
- java数据结构和算法——图的深度优先(DFS)遍历
目录 一.图的遍历介绍 二.图的深度优先搜索(Depth First Search) 三.图的深度优先遍历算法步骤 四.图的深度优先遍历示例需求 五.图的深度优先遍历代码示例 一.图的遍历介绍 所谓图 ...
- 数据结构 图 简单入门 最小生成树 深度优先搜索 广度优先搜索
早上上班路上,看到一则新闻,广州的.新闻说的是一个辅警在路边查过往路人的身份证,其中有一个记者,就询问,根据哪条法律要查询他的身份证.辅警当然不知道(事后据说,就是闲着无聊,查着玩的!),也肯定不会认 ...
- 算法导论——DFS深度优先搜索
2019独角兽企业重金招聘Python工程师标准>>> package org.loda.graph;import org.loda.structure.Stack;/*** * @ ...
- 栈和队列8 - 数据结构和算法30
栈和队列8 让编程改变世界 Change the world by program 队列的顺序存储结构 为什么小甲鱼上节课说队列的实现上我们更愿意用链式存储结构来存储? 我们先按照应有的思路来考虑下如 ...
- C++算法复习之深度优先搜索(dfs)与解救小扣题解
深度优先搜索(Depth First Search) 深度优先搜索,一种非常快速的 搜索方式,接下来我们将主要讨论: 搜索方式 本质 应用 搜索方式 深度优先搜索原本是专门为树形图与无向图准备的,如下 ...
- 算法笔记01——深度优先搜索(DFS)/宽度优先搜索(BFS)
深度优先搜索(DFS) 从某个状态开始,不断地转移状态直到无法转移,然后回退到前一步的状态,继续转移到其他状态,如此不断重复,直至找到最终的解.深度优先搜索从最开始的状态出发,遍历所有可以到达的状态. ...
- 数据结构与算法--图的广度优先搜索 (BFS)
广度优先搜索即是 一种"地毯式"层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索. BFS解决的最短路径问题. 采用BFS进行遍历的话,需要依赖队列,先进先 ...
最新文章
- 20130410 现阶段的学习状况
- ie6多文件上传_一个好的“文件上传”功能必须要注意的这些点你都知道吗?
- C语言中| 按位或, || 逻辑或。
- Google Gears 指南
- node版本查看管理工具
- 包含JS交互的混淆出错
- python游戏开发工程师_Python开发工程师-入门与实战视频课程
- 微服务架构开发实战:如何实现微服务的自动扩展?
- python是什么语言
- 用php写成绩switch,帮我看一下,这个php的switch我写的对不对?
- 【个人笔记】OpenCV4 C++ 图像处理与视频分析 03课
- jpegNPP编译为so
- python选择题题库百度文库_Python题库
- Ansible Inventory内置参数
- 从零开始学习ThingJS之创建App对象
- 典型相关分析及R应用
- redis5大数据结构
- 3D max新增超级阵列功能Array !
- 蝴蝶效应,鳄鱼法则,罗森塔尔效应,帕金森定律,手表定律,破窗理论,晕轮效应 ,霍桑效应,二八定律,木桶理论,马太效应,踢猫效应…………
- 【LOJ573】「LibreOJ NOI Round #2」单枪匹马
热门文章
- 计算机毕业设计JAVA企业售后服务管理系统mybatis+源码+调试部署+系统+数据库+lw
- 扑克牌游戏“快算24”算法
- java根据业务排序利用Comparator.comparing自定义排序规则
- 中南大学2021计算机专硕复试分数线,中南大学2021年考研复试分数线
- Too many files with unapproved license
- 笔记———计算机网络原理(二)
- 希腊神话传说中的诸神
- 【微信小程序】wx.request请求后success回调的数据无法显示到页面上
- 力扣周赛 第280场 Java题解
- 【机器学习】【特征选择】4.嵌入法Embedded