BFS
(Breadth-First Search)是一种用于遍历或搜索树(tree)或图(graph)的算法。
这个算法从根(或某个任意节点)开始,并探索最近的邻居节点,
然后再探索那些节点的邻居节点,如此类推,直到找到目标或已经探索了所有可达的节点。
BFS 是一种广度优先的搜索策略,也就是说,它首先访问所有相邻的节点,然后才访问下一层的节点。
BFS:可以搜到最短路径,是一圈一圈的进行搜索。所以题目若要求最短路问题,一定是BFS而非DFS。
题目:
AcWing 844. 走迷宫 - AcWing
思路:
第几层被扩展到,即距离原点多远:
当前所在的节点有四种可能性:向上(-1,0)、下(1,0)、左(0,-1)、右(0,1)扩展(这里的扩展时对应的是矩阵),我们分别枚举即可。
这里用两个一维数组来简化代码:dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}
不可扩展的情况有1、扩展时下一节点数值为1,2、扩展时下一节点之前已经被其他路径扩展了(因为是要求最优解,之前被走过的路重复走会增加次数)3、扩展到边界了。
代码:
①:数组模拟队列
#include<iostream>
#include<algorithm>
#include<cstring>
const int N=105;
using namespace std;
int n,m;
//g数组存的是地图
int g[N][N];
//d数组存的是地图上的每一个点到起点的距离
int d[N][N];
//定义一个数组q[N],用来存放点的坐标
pair<int,int> q[N*N];
int bfs()
{
//一开始队列为空
int hh=0,tt=0;
//起点坐标为(0,0)
q[0]={0,0};
//先将所有数组d[][]的值初始化为-1,-1表示没走过
memset(d,-1,sizeof d);
//一开始的起点已经走过了
d[0][0]=0;
//偏移量
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
while(hh <= tt)
{
/*
每次取出队头(队头即为bfs宽搜时遇到的最新的一个点),
然向上、下、左、右四个方向进行扩展
*/
pair<int,int> t=q[hh++];
//每一次枚举一下四个方向
for(int i=0;i<4;i++)
{
//找到下一个点的坐标,由于分别枚举了四个不同的方向,
//所以四个放向都会囊括在内,并且找到最优解
int x=t.first+dx[i],y=t.second+dy[i];
/*
x、y没出边界,并且向这个方向扩展时g[x][y]==0(符合题意时),
并且扩展后的点之前没有被用过时(最短路,一个点只走一次时所走的路径最短)
*/
if(x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1)
{
//那么这个点的距离等于上个点到原点的距离+1;
d[x][y] = d[t.first][t.second]+1;
//把这个点的坐标加进来
q[ ++ tt] = {x,y};
}
}
}
return d[n-1][m-1];
}
int main()
{
cin >> n >> m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
②:队列:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105;
int n,m;
int g[N][N];
int d[N][N];
typedef pair<int,int> PII;
PII p[N*N];
int dfs()
{
int hh=0,tt=0;
memset(d,-1,sizeof d);
d[0][0]=0;
p[0]={0,0};
while(hh<=tt)
{
PII t=p[hh++];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1)
{
d[x][y]=d[t.first][t.second]+1;
p[++tt]={x,y};
}
}
}
return d[n-1][m-1];
}
int main()
{
cin >> n >> m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin >> g[i][j];
cout << dfs();
return 0;
}
题目:
845. 八数码 - AcWing题库
思路:
这里每种不同的状态我们直接用字符串来记录,然后利用unordered_map<string, int> d;将每种字符串映射到不同的int值里,来达到记录每种状态(字符串)的目的。
'x'的上、下、左、右 每次向空格'x'里移动一次,称为一个操作
我们可以把所有状态看成是图论当中的一个节点
然后每变换一次就会成为一个新的节点
最终的状态为最终节点
上一个节点与下一个节点的连线的权重为1
那么从最开始的节点到最终的节点的长度就是最短路径
难点:
①状态表示比较复杂:如何将每一个矩阵的状态存到队列里面
②如何来记录每一个状态的距离
③如何做状态转移{
1)先恢复成矩阵的形式,2)再将空格x的上下左右分别枚举,移到x的位置上去
3)再将矩阵恢复成字符串的形式,这时就会形成一个新的字符串了(如果之前没有被形成的话)
}
①可以用一个字符串来表示这样一种状态。
队列直接用queue<string>来表示。②距离直接用哈希表来记录:unordered_map<string>来存。
代码:
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<queue>
using namespace std;
int bfs(string start)
{
//end是最终要求的状态,不同的字符串对应不同的状态
string end="12345678x";
queue<string> q;
//距离数组,每一个string状态映射一个int值
unordered_map<string,int> d;
q.push(start);
//一开始起点到终点的距离为0
d[start]=0;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
while(!q.empty())
{
//记录当前的状态
auto t=q.front();
q.pop();
//记录当前的坐标
int distance=d[t];
//如果找到了,返回距离
if(t==end) return distance;
//状态转移,看一下当前的状态能变成哪些状态
//先找到'x'在字符串中所对应的下标
int k=t.find('x');
int x=k/3,y=k%3;//找到'x'的坐标,下一步的操作就是移动'x'的上、下、左、右坐标,
//进而形成新的状态
for(int i=0;i<4;i++)
{
//上下左右的坐标{a,b}
int a=x+dx[i],b=y+dy[i];
if(a>=0 && a<3 && b>=0 && b<3)
{
//'x'所在字符串为t[k],
//二维转换成一维:a*3+b
//交换两种状态(更新当前的状态,使其等于最新的状态)
swap(t[k],t[a*3+b]);
//如果更新完之后的t之前没有被搜过的话,即更新完后的状态之前没出现过的话
if(!d.count(t))
{
//更新完后的状态到原始状态的距离=之前的距离+1;
d[t]=distance+1;
//新的状态加到队列当中去
q.push(t);
}
//恢复原状,以便下次循环判断新的状态
swap(t[k],t[a*3+b]);
}
}
}
//没找到
return -1;
}
int main()
{
//string表示状态
string start;
for(int i=0;i<9;i++)
{
char c;
cin >> c;
//记录初始状态
start+=c;
}
cout << bfs(start) << endl;
return 0;
}
tips:
在C++中,unordered_map<string, int> d; 定义了一个名为 d 的 unordered_map(无序映射),
其键(key)是 string 类型,值(value)是 int 类型。这个 unordered_map 不保证元素的顺序与插入顺序相同,
但它允许你以非常接近O(1)的平均时间复杂度来查找、插入和删除元素(基于哈希表实现)。
这个 unordered_map 本身并不返回 int 值。
它是一个容器,可以存储多个键值对(key-value pairs),
其中每个键是唯一的 string,每个键映射到一个 int 值。
。
向上(-1,0)、下(1,0)、左(0,-1)、右(0,1),这里可以用两个一维数组来简化代码:dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}
(dx[1],dy[1])即代表向上