题目链接:力扣
首先给出单源广度优先搜索的基本思路,参考广度优先搜索(BFS)_PL_涵的博客-CSDN博客
#include<iostream>
#include<queue>
#include<unordered_map>
using namespace std;
const int N=3;
unordered_map<string,int> d; //距离数组,存储每个状态到起始状态的距离(即最初的矩阵要经过几次变化达到现在的状态)
queue<string> q; //状态队列,用于存储每一个要进行bfs的状态
//对每个state做bfs
int bfs(string state){
const string end="12345678x"; //正确排列,即结束状态
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
while(!q.empty()){ //还有状态能被bfs时
//取队头状态,开始bfs
auto t=q.front();
q.pop();
int distance=d[t];
if(t==end) return distance; //若已找到正确排列(即当前队头就是),就返回
int pos=t.find('x');
int x=pos/N,y=pos%N; //把x在字符串中的位置转成在N*N矩阵中的位置
for(int i=0;i<4;i++){//当前队头不是正确排列,还没找到,就bfs当前队头的状态,看看有没有新状态可以添加进队列(后续会对这些新状态进行同样的bfs过程)
int a=x+dx[i],b=y+dy[i];
if(a>=0 && a<N && b>=0 && b<N){
swap(t[pos],t[a*N+b]); //新状态:把当前状态中的x和其前/后/左/右元素互换(如果这些元素存在,未越界)
//count()返回指定参数key的键的元素数,由于unordered_map不允许重复,因此返回1/0
if(!d.count(t)){//如果上面得到的新状态没在unordered_map中出现过,说明是第一次出现,则加入队列
q.push(t);
d[t]=distance+1;
}
swap(t[pos],t[a*N+b]); //bfs完一种状态(如在最初队头状态t的基础上,换了x和其左边的一个元素),恢复现场,开始下一个状态的bfs(在最初队头状态t的基础上,换了x和其上/下/右边的一个元素)
}
}
}
return -1; //循环完了都没发现正确排列,就返回-1
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
string start;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
char c;
cin>>c;
start+=c;
}
}
//初始化距离数组和状态队列
d[start]=0;
q.push(start);
cout<<bfs(start)<<endl;
return 0;
}
而多源广度优先搜索相比于单源,无非就是最初初始化队列时,单源只需要加入一个源起点,而多源则需要把所有的多个起点都加入队列中,后面的步骤就差不多了。上代码:
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
//m*n网格(矩阵)
int m=grid.size();
int n=grid[0].size();
queue<pair<int,int>> q;
int cnt=0; //新鲜橘子的数量
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==2) q.push({i,j}); //腐烂的橘子入队
else if(grid[i][j]==1) cnt++;
}
}
//多源广度优先搜索
int time=0; //橘子全腐烂需要的时间
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
while(!q.empty()){
auto t=q.front();
q.pop();
for(int i=0;i<4;i++){
int x=t.first+dx[i];
int y=t.second+dy[i];
if(x>=0 && x<m && y>=0 && y<n && grid[x][y]==1){
cnt--;
q.push({x,y});
grid[x][y]=grid[t.first][t.second]+1; //记录每个网格中橘子变腐烂时的时间(=经过的时间+2, 2是腐烂橘子源单元格内的时间). 这里时间一定>=2, 和1/0不冲突, 因此后面不会产生把更新过的橘子认为是没腐烂过的橘子/空网格的误区。因此可以用来判断该点是否被访问过
time=grid[x][y]-2; //上面+2, 这边要-2才能还原出经过的时间
}
}
}
if(!cnt) return time;
return -1;
}
};
本题的代码有一个很巧妙的点:在判断一个点是否被访问过时,不需要单独开一个数组来记录访问状态,而是直接在原数组上修改值就可以
grid[x][y]=grid[t.first][t.second]+1; //记录每个网格中橘子变腐烂时的时间(=经过的时间+2, 2是腐烂橘子源单元格内的时间). 这里时间一定>=2, 和1/0不冲突, 因此后面不会产生把更新过的橘子认为是没腐烂过的橘子/空网格的误区。因此可以用来判断该点是否被访问过