广度优先搜索(也称宽度优先搜索,缩写BFS,如下采用广度来描述)是连通图的一种遍历策略。由于它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,故得名。 算法
通常能够用它作什么呢?一个最直观经典的例子就是走迷宫,咱们从起点开始,找出到终点的最短路程,不少最短路径算法就是基于广度优先的思想成立的。编程
算法导论里边会给出很多严格的证实,我想尽可能写得通俗一点,所以采用一些直观的讲法来假装成证实,关键的point可以帮你get到就好。数组
刚刚说的广度优先搜索是连通图的一种遍历策略,那就有必要将图先简单解释一下。网络
图2-1 连通图示例图app
如图2-1所示,这就是咱们所说的连通图,这里展现的是一个无向图,连通即每2个点都有至少一条路径相连,例如V0到V4的路径就是V0->V1->V4。spa
通常咱们把顶点用V缩写,把边用E缩写。.net
经常咱们有这样一个问题,从一个起点开始要到一个终点,咱们要找寻一条最短的路径,从图2-1举例,若是咱们要求V0到V6的一条最短路(假设走一个节点按一步来算)【注意:此处你能够选择不看这段文字直接看图3-1】,咱们明显看出这条路径就是V0->V2->V6,而不是V0->V3->V5->V6。先想一想你本身刚刚是怎么找到这条路径的:首先看跟V0直接链接的节点V一、V二、V3,发现没有V6,进而再看刚刚V一、V二、V3的直接链接节点分别是:{V0、V4}、{V0、V一、V6}、{V0、V一、V5}(这里画删除线的意思是那些顶点在咱们刚刚的搜索过程当中已经找过了,咱们不须要从新回头再看他们了)。这时候咱们从V2的连通节点集中找到了V6,那说明咱们找到了这条V0到V6的最短路径:V0->V2->V6,虽然你再进一步搜索V5的链接节点集合后会找到另外一条路径V0->V3->V5->V6,但显然他不是最短路径。指针
你会看到这里有点像辐射形状的搜索方式,从一个节点,向其旁边节点传递病毒,就这样一层一层的传递辐射下去,知道目标节点被辐射中了,此时就已经找到了从起点到终点的路径。htm
咱们采用示例图来讲明这个过程,在搜索的过程当中,初始全部节点是白色(表明了全部点都还没开始搜索),把起点V0标志成灰色(表示即将辐射V0),下一步搜索的时候,咱们把全部的灰色节点访问一次,而后将其变成黑色(表示已经被辐射过了),进而再将他们所能到达的节点标志成灰色(由于那些节点是下一步搜索的目标点了),可是这里有个判断,就像刚刚的例子,当访问到V1节点的时候,它的下一个节点应该是V0和V4,可是V0已经在前面被染成黑色了,因此不会将它染灰色。这样持续下去,直到目标节点V6被染灰色,说明了下一步就到终点了,不必再搜索(染色)其余节点了,此时能够结束搜索了,整个搜索就结束了。而后根据搜索过程,反过来把最短路径找出来,图3-1中把最终路径上的节点标志成绿色。blog
整个过程的实例图如图3-1所示。
初始所有都是白色(未访问)
即将搜索起点V0(灰色)
已搜索V0,即将搜索V一、V二、V3
……终点V6被染灰色,终止
找到最短路径
图3-1 寻找V0到V6的过程
图3-2 广度优先搜索的流程图
在写具体代码以前有必要先举个实例,详见第4节。
第一节就讲过广度优先搜索适用于迷宫类问题,这里先给出POJ3984《迷宫问题》。
《迷宫问题》
定义一个二维数组:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
它表示一个迷宫,其中的1表示墙壁,0表示能够走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
题目保证了输入是必定有解的。
也许你会问,这个跟广度优先搜索的图怎么对应起来?BFS的第一步就是要识别图的节点跟边!
节点就是某种状态,边就是节点与节点间的某种规则。
对应于《迷宫问题》,你能够这么认为,节点就是迷宫路上的每个格子(非墙),走迷宫的时候,格子间的关系是什么呢?按照题目意思,咱们只能横竖走,所以咱们能够这样看,格子与它横竖方向上的格子是有连通关系的,只要这个格子跟另外一个格子是连通的,那么两个格子节点间就有一条边。
若是说本题再修改为斜方向也能够走的话,那么就是格子跟周围8个格子均可以连通,因而一个节点就会有8条边(除了边界的节点)。
对应于题目的输入数组:
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
咱们把节点定义为(x,y),(x,y)表示数组maze的项maze[x][y]。
因而起点就是(0,0),终点是(4,4)。按照刚刚的思路,咱们大概手工梳理一遍:
初始条件:
起点Vs为(0,0)
终点Vd为(4,4)
灰色节点集合Q={}
初始化全部节点为白色节点
开始咱们的广度搜索!
手工执行步骤【PS:你能够直接看图4-1】:
1.起始节点Vs变成灰色,加入队列Q,Q={(0,0)}
2.取出队列Q的头一个节点Vn,Vn={0,0},Q={}
3.把Vn={0,0}染成黑色,取出Vn全部相邻的白色节点{(1,0)}
4.不包含终点(4,4),染成灰色,加入队列Q,Q={(1,0)}
5.取出队列Q的头一个节点Vn,Vn={1,0},Q={}
6.把Vn={1,0}染成黑色,取出Vn全部相邻的白色节点{(2,0)}
7.不包含终点(4,4),染成灰色,加入队列Q,Q={(2,0)}
8.取出队列Q的头一个节点Vn,Vn={2,0},Q={}
9.把Vn={2,0}染成黑色,取出Vn全部相邻的白色节点{(2,1), (3,0)}
10.不包含终点(4,4),染成灰色,加入队列Q,Q={(2,1), (3,0)}
11.取出队列Q的头一个节点Vn,Vn={2,1},Q={(3,0)}
12. 把Vn={2,1}染成黑色,取出Vn全部相邻的白色节点{(2,2)}
13.不包含终点(4,4),染成灰色,加入队列Q,Q={(3,0), (2,2)}
14.持续下去,知道Vn的全部相邻的白色节点中包含了(4,4)……
15.此时得到了答案
起始你很容易模仿上边过程走到终点,那为何它就是最短的呢?
怎么保证呢?
咱们来看看广度搜索的过程当中节点的顺序状况:
图4-1 迷宫问题的搜索树
你是否观察到了,广度搜索的顺序是什么样子的?
图中标号即为咱们搜索过程当中的顺序,咱们观察到,这个搜索顺序是按照上图的层次关系来的,例如节点(0,0)在第1层,节点(1,0)在第2层,节点(2,0)在第3层,节点(2,1)和节点(3,0)在第3层。
咱们的搜索顺序就是第一层->第二层->第三层->第N层这样子。
咱们假设终点在第N层,所以咱们搜索到的路径长度确定是N,并且这个N必定是所求最短的。
咱们用简单的反证法来证实:假设终点在第N层上边出现过,例如第M层,M<N,那么咱们在搜索的过程当中,确定是先搜索到第M层的,此时搜索到第M层的时候发现终点出现过了,那么最短路径应该是M,而不是N了。
因此根据广度优先搜索的话,搜索到终点时,该路径必定是最短的。
我给出如下代码用于解决上述题目(仅仅只是核心代码):
[cpp] view plain copy
为了方便适用于大多数的题解,抽取核心代码以下:
[cpp] view plain copy
对于一个题目来讲,要标志节点是否访问过,用数组是一种很快速的方法,但有时数据量太大,很难用一个大数组来记录时,采用hash是最好的作法。实际上visit数组在这里也是充当hash的做用。(PS:至于hash是什么?得本身去了解,它的做用是在O(1)的时间复杂度内取出某个值)
给定序列1 2 3 4 5 6,再给定一个k,咱们给出这样的操做:对于序列,咱们能够将其中k个连续的数所有反转过来,例如k = 3的时候,上述序列通过1步操做后能够变成:3 2 1 4 5 6 ,若是再对序列 3 2 1 4 5 6进行一步操做,能够变成3 4 1 2 5 6.
那么如今题目就是,给定初始序列,以及结束序列,以及k的值,那么你可以求出从初始序列到结束序列的转变至少须要几步操做吗?
本题能够采用BFS求解,已经给定初始状态跟目标状态,要求之间的最短操做,其实也很明显是用BFS了。
咱们把每次操做完的序列当作一个状态节点。那每一次操做就产生一条边,这个操做就是规则。
假设起始节点是:{1 2 3 4 5 6},终点是:{3 4 1 2 5 6}
去除队列中的起始节点时,将它的相邻节点加入队列,其相邻节点就是对其操做一次的全部序列:
{3 2 1 4 5 6}、{1 4 3 2 5 6}、{1 2 5 4 3 6}、{1 2 3 6 5 4}
而后继续搜索便可获得终点,此时操做数就是搜索到的节点所在的层数2。
题目分类来自网络:
sicily:1048 1444 1215 1135 1150 1151 1114
pku:1136 1249 1028 1191 3278 1426 3126 3087 3414
假设图有V个顶点,E条边,广度优先搜索算法须要搜索V个节点,所以这里的消耗是O(V),在搜索过程当中,又须要根据边来增长队列的长度,因而这里须要消耗O(E),总得来讲,效率大约是O(V+E)。
其实最影响BFS算法的是在于Hash运算,咱们前面给出了一个visit数组,已经算是最快的Hash了,但有些题目来讲可能Hash的速度要退化到O(lgn)的复杂度,固然了,具体仍是看实际状况的。
BFS适合此类题目:给定初始状态跟目标状态,要求从初始状态到目标状态的最短路径。
进而扩展的话就是双向广度搜索算法,顾名思义,便是从起点跟终点分别作广度优先搜索,直到他们的搜索过程当中有一个节点相同了,因而就找到了起点跟终点的一条路径。
腾讯笔试题目:假设每一个人平均是有25个好友,根据六维理论,任何人之间的联系必定能够经过6我的而间接认识,间接经过N我的认识的,那他就是你的N度好友,如今要你编程验证这个6维理论。
此题若是直接作广度优先搜索,那么搜索的节点数可能达到25^6,若是是用双向的话,两个树分别只须要搜索到3度好友便可,搜索节点最多为25^3个,可是用双向广度算法的话会有一个问题要解决,就是你如何在搜索的过程当中判断第一棵树中的节点跟第二棵树中的节点有相同的呢?按个人理解,能够用Hash,又或者放进队列的元素都是指向原来节点的指针,而每一个节点加入一个color的属性,这样再搜索过程当中就能够根据节点的color来判断是否已经被搜索过了。