赞
踩
(Breadth-First Search)是一种用于遍历或搜索树(tree)或图(graph)的算法。
这个算法从根(或某个任意节点)开始,并探索最近的邻居节点,
然后再探索那些节点的邻居节点,如此类推,直到找到目标或已经探索了所有可达的节点。
BFS 是一种广度优先的搜索策略,也就是说,它首先访问所有相邻的节点,然后才访问下一层的节点。
BFS:可以搜到最短路径,是一圈一圈的进行搜索。所以题目若要求最短路问题,一定是BFS而非DFS。
第几层被扩展到,即距离原点多远:
当前所在的节点有四种可能性:向上(-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<algorithm>
- #include<cstring>
- #include<queue>
-
- using namespace std;
-
- const int N=105;
- int n,m;
- int g[N][N],d[N][N];
- //队列q,类型为pair <int,int>型
- queue<pair <int,int>> q;
- //定义一个二维数组,来记录行走路径
- pair <int,int> Prev[N][N];
-
- int bfs()
- {
- //初始化所有至起点的距离都为-1
- memset(d,-1,sizeof d);
- //一开始起点至起点的距离为1
- d[0][0]=0;
- //起点坐标记录下来
- q.push({0,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=dx[i]+t.first,y=dy[i]+t.second;
- //如果符合题意
- 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;
- //记录当前坐标
- Prev[x][y]=t;
- //将这一个点放至队列
- q.push({x,y});
- }
- }
- }
- //从后往前
- int x=n-1,y=m-1;
- while(x||y)
- {
- //输出当前坐标
- cout << x<< " "<< y << endl;
- auto t=Prev[x][y];
- //向前走。因为这里Prev记录的是这段路径的坐标,所以x、y是沿着这条路径向前走的
- x=t.first,y=t.second;
-
- }
- //输出题目要求的点到原点的距离即可
- 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();
- return 0;
- }
这里每种不同的状态我们直接用字符串来记录,然后利用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之前没有被搜过的话,即更新完后的状态之前没出现过的话
- /*
- 在C++中,std::unordered_map提供了一个成员函数count,该函数用于检查映射中是否存在具有特定键的元素。
- 如果映射中存在该键的元素,count函数返回1;如果不存在,返回0。
- */
- 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;
-
-
- }
在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])即代表向上
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。