写在前面
hello小伙伴们,我们又见面啦~上一篇文章蓝桥杯算法竞赛备考(一)—— 搜索专题(上)(C++)得到了小伙伴们的认可,我真的是喜出望外,小伙伴们的支持是我最大的动力噢~
好啦废话不多说,让我们进入今天的算法学习吧~
今天讲的主要有以下几点内容噢!
- 广度优先搜索的基本思想
- BFS的模板
- BFS经典例题
- 记忆化搜索
文章目录
一、广度优先搜索(BFS)
深度优先搜索会优先考虑搜索的深度,就是找不到答案不回头。 当答案在解答树中比较稀疏的时候,DFS会陷入过深的情况,搜索很多无效状态,一时半会找不到解,这时候我们就可以用BFS啦~在连通性,最短路问题中常优先考虑BFS。
BFS
和DFS
搜索的顺序是不一样的。DFS
前面已经讲到过,是以深度为第一关键词的,也就是说从某个结点出发总是先选择其中一个分支前进,而不管其他的分支,直到碰到死胡同才停止。然后回溯到上一个结点选择其他的分支。
而BFS
搜索的顺序是这样的,从某个结点出发,总是先依次访问这个结点能直接到达的所有结点,然后再访问这些结点所能直接到达的所有结点,依次类推,直到所有的结点都访问完毕。
下面呢我们看一个迷宫,小伙伴们可能会对BFS
有一个清晰的认识。
BFS
搜索的顺序是这个样子滴~首先从起点A
出发,它能到达的所有结点有B
和C
(第二层),因此接下来去访问B
,B
能直接到达D
和E
(第三层),接下来访问C
,C
能直接到达F
和G
(第三层)。第二层访问结束,接下来访问D
,D
能直接到达I
和G
和H
(第四层)。接下来访问E
,E
能直接到达M
和L
和K
(第四层),接着访问F
,F
是死胡同。接下来访问G
,G
是终点,搜索结束。
至于那些未访问过的结点,我们就不用去管它了。
可以看出层数就是从结点A出发到达其他结点的最小步数。广度优先搜索会优先考虑每种状一个专题态的和初始状态的距离,也就是与初始状态越接近的就会越先考虑。这就能保证一个状态被访问时一定是采用的最短路径。
其实DFS和BFS都可以从树的角度去分析。
DFS
相当于树的先序遍历,BFS
相当于树的层次遍历。
DFS
搜索顺序是123456
,BFS
搜索顺序是125346
。
相信小伙伴们很容易就能看出来~
二、用队列来实现BFS(模板)
BFS
的搜索过程很像一个队列,队列的特点就是先进先出。谁入队早就先访问谁。
我们简单的来分析一下队列模拟的过程~
- 先把起点
A
入队,取出队首A
,将A
直接到达的B
和C
入队。队列元素有{B
,C
}。 - 再将队首
B
取出,将B
直接到达的D
和E
入队。此时队列元素有{C
,D
,E
}。 - 再将队首
C
出队,将C
直接到达的F
和G
入队。此时队列元素有{D
,E
,F
,G
}。 - 再将队首
D
出队,将D
直接到达的I
和J
和H
入队。此时队列元素有{E
,F
,G
,I
,J
,H
}。 - 再将队首
E
出队,将E
直接到达的M
和L
和K
入队。此时队列元素有{F
,G
,I
,J
,H
,M
,L
,K
。 - 再将队首
F
出队,F
没有直接相连的新节点,此时队列元素有{G
,I
,J
,H
,M
,L
,K
}。 - 再将
G
出队,找到终点,算法结束。
下面给出BFS
的模板。
还是那句话多记些模板对比赛是很有帮助的噢~
void bfs()
{
queue<int> q;//一般用stl库中的queue来实现队列比较方便
q.push(起点S);//将初始状态入队
标记初始状态已入队。
while(!q.empty())//队列不为空就执行入队出队操作
{
top = q.front();//取出队首
q.pop();//队首出队
for (枚举所有可扩展的状态)
{
if (check())//状态合法
{
q.push(temp);//状态入队
标记成已入队。
}
}
}
对模板做几点说明
- 建议大家用
stl
中的queue
表示队列。操作方便。头文件要加#include<queue>
- 要区分
front()
和pop()
的区别,front
的返回值是队首元素,而pop
没有返回值。 - 我们常用一个新变量来储存队首元素,以便在它出队后方便下面的操作。
top = q.front()
。
还有一个问题,我们在使用BFS
时会设一个inq
数组记录结点有没有入队,为什么不是记录结点有没有访问呢,不知道小伙伴们想没想过这个问题。
区别在于,设置成结点是否已访问,会导致有些点在队列中但是还没有被访问,由于其他结点可以到达它而将这个结点入队,导致很多结点重复入队,这是很不明智的。
以后也会专门出一篇讲解比赛中常用的
STL
标准模板库。
上面对BFS
的讲解参考胡凡《算法笔记》并补充了自己对BFS
的理解。
三、BFS经典例题
1. 模板题——迷宫最短路
题目分析
该题是要求从起点到终点的最小步数。我们可以有两种解决方案,DFS
和BFS
。
- 可以
DFS
所有从起点到终点的所有路径,然后取路径最短的那条。 BFS
是很理所当然的,因为求的是最小步数,涉及最短问题。
我们先来看一下BFS
该如何解决这个问题。因为是板子题嘛直接照着模板敲就好了~
AC代码(BFS)
//bfs 广度优先搜索
//迷宫问题 求解最短路径
#include <iostream>
#include <queue>
using namespace std;
const int N = 100;
int n, m;//n行m列
char maze[N][N];//迷宫
bool inq[N][N];//记录位置(x,y)是否入队
int x[4] = {
-1, 1, 0, 0};//增量数组
int y[4] = {
0, 0, -1, 1};
struct node
{
int x, y;
int step;
}s, t, Node;//s起点 t终点 Node临时节点
bool test(int x, int y)//判断位置(x,y)是否有效
{
//以下三种情况不能入队
if (x >= n || x < 0 || y >= m || y < 0) return false;//出界
if (maze[x][y] == '*') return false;//墙壁
if (inq[x][y] == true) return false;//已入队
return true;//其余情况可以将该点入队
}
int bfs()
{
queue<node> q;//定义队列
q.push(s);//将起点入队
while(!q.empty())//队列不为空执行
{
node top = q.front();//取出队首元素
q.pop();//出队
//inq[s.x][s.y] = true;//将起点设为已入队
if (top.x == t.x && top.y == t.y)//到达终点
{
return top.step;返回终点的层数,即最少步数