当前位置:   article > 正文

.BFS.

.BFS.

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、扩展到边界了。

代码:

①:数组模拟队列

  1. #include<iostream>
  2. #include<algorithm>
  3. #include<cstring>
  4. const int N=105;
  5. using namespace std;
  6. int n,m;
  7. //g数组存的是地图
  8. int g[N][N];
  9. //d数组存的是地图上的每一个点到起点的距离
  10. int d[N][N];
  11. //定义一个数组q[N],用来存放点的坐标
  12. pair<int,int> q[N*N];
  13. int bfs()
  14. {
  15. //一开始队列为空
  16. int hh=0,tt=0;
  17. //起点坐标为(0,0)
  18. q[0]={0,0};
  19. //先将所有数组d[][]的值初始化为-1,-1表示没走过
  20. memset(d,-1,sizeof d);
  21. //一开始的起点已经走过了
  22. d[0][0]=0;
  23. //偏移量
  24. int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
  25. while(hh <= tt)
  26. {
  27. /*
  28. 每次取出队头(队头即为bfs宽搜时遇到的最新的一个点),
  29. 然向上、下、左、右四个方向进行扩展
  30. */
  31. pair<int,int> t=q[hh++];
  32. //每一次枚举一下四个方向
  33. for(int i=0;i<4;i++)
  34. {
  35. //找到下一个点的坐标,由于分别枚举了四个不同的方向,
  36. //所以四个放向都会囊括在内,并且找到最优解
  37. int x=t.first+dx[i],y=t.second+dy[i];
  38. /*
  39. x、y没出边界,并且向这个方向扩展时g[x][y]==0(符合题意时),
  40. 并且扩展后的点之前没有被用过时(最短路,一个点只走一次时所走的路径最短)
  41. */
  42. if(x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1)
  43. {
  44. //那么这个点的距离等于上个点到原点的距离+1;
  45. d[x][y] = d[t.first][t.second]+1;
  46. //把这个点的坐标加进来
  47. q[ ++ tt] = {x,y};
  48. }
  49. }
  50. }
  51. return d[n-1][m-1];
  52. }
  53. int main()
  54. {
  55. cin >> n >> m;
  56. for(int i=0;i<n;i++)
  57. for(int j=0;j<m;j++)
  58. cin >> g[i][j];
  59. cout << bfs() << endl;
  60. return 0;
  61. }

②:队列:

BFS查询路径:

代码:
  1. #include<iostream>
  2. #include<algorithm>
  3. #include<cstring>
  4. #include<queue>
  5. using namespace std;
  6. const int N=105;
  7. int n,m;
  8. int g[N][N],d[N][N];
  9. //队列q,类型为pair <int,int>型
  10. queue<pair <int,int>> q;
  11. //定义一个二维数组,来记录行走路径
  12. pair <int,int> Prev[N][N];
  13. int bfs()
  14. {
  15. //初始化所有至起点的距离都为-1
  16. memset(d,-1,sizeof d);
  17. //一开始起点至起点的距离为1
  18. d[0][0]=0;
  19. //起点坐标记录下来
  20. q.push({0,0});
  21. //向上、右、下、左的偏移量,用两个一维数组表示出来
  22. int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
  23. while(!q.empty())
  24. {
  25. //取出队头,保证是圈式延伸
  26. auto t=q.front();
  27. //记录之后删除,确保正确延伸
  28. q.pop();
  29. for(int i=0;i<4;i++)
  30. {
  31. int x=dx[i]+t.first,y=dy[i]+t.second;
  32. //如果符合题意
  33. if(x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1 )
  34. {
  35. //下一个点到原点的距离等于上一个点到原点的距离+1;
  36. d[x][y]=d[t.first][t.second]+1;
  37. //记录当前坐标
  38. Prev[x][y]=t;
  39. //将这一个点放至队列
  40. q.push({x,y});
  41. }
  42. }
  43. }
  44. //从后往前
  45. int x=n-1,y=m-1;
  46. while(x||y)
  47. {
  48. //输出当前坐标
  49. cout << x<< " "<< y << endl;
  50. auto t=Prev[x][y];
  51. //向前走。因为这里Prev记录的是这段路径的坐标,所以x、y是沿着这条路径向前走的
  52. x=t.first,y=t.second;
  53. }
  54. //输出题目要求的点到原点的距离即可
  55. return d[n-1][m-1];
  56. }
  57. int main()
  58. {
  59. cin >> n >> m;
  60. for(int i=0;i<n;i++)
  61. for(int j=0;j<m;j++)
  62. cin >> g[i][j];
  63. cout << bfs();
  64. return 0;
  65. }

题目:

845. 八数码 - AcWing题库

思路: 

这里每种不同的状态我们直接用字符串来记录,然后利用unordered_map<string, int> d;将每种字符串映射到不同的int值里,来达到记录每种状态(字符串)的目的。

'x'的上、下、左、右 每次向空格'x'里移动一次,称为一个操作
我们可以把所有状态看成是图论当中的一个节点
然后每变换一次就会成为一个新的节点
最终的状态为最终节点
上一个节点与下一个节点的连线的权重为1
那么从最开始的节点到最终的节点的长度就是最短路径
难点:
①状态表示比较复杂:如何将每一个矩阵的状态存到队列里面
②如何来记录每一个状态的距离
③如何做状态转移{
    1)先恢复成矩阵的形式,2)再将空格x的上下左右分别枚举,移到x的位置上去
    3)再将矩阵恢复成字符串的形式,这时就会形成一个新的字符串了(如果之前没有被形成的话)
    
}
  ①可以用一个字符串来表示这样一种状态。
  队列直接用queue<string>来表示。②距离直接用哈希表来记录:unordered_map<string>来存。

代码:
  1. #include<iostream>
  2. #include<algorithm>
  3. #include<unordered_map>
  4. #include<queue>
  5. using namespace std;
  6. int bfs(string start)
  7. {
  8. //end是最终要求的状态,不同的字符串对应不同的状态
  9. string end="12345678x";
  10. queue<string> q;
  11. //距离数组,每一个string状态映射一个int值
  12. unordered_map<string,int> d;
  13. q.push(start);
  14. //一开始起点到终点的距离为0
  15. d[start]=0;
  16. int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
  17. while(!q.empty())
  18. {
  19. //记录当前的状态
  20. auto t=q.front();
  21. q.pop();
  22. //记录当前的坐标
  23. int distance=d[t];
  24. //如果找到了,返回距离
  25. if(t==end) return distance;
  26. //状态转移,看一下当前的状态能变成哪些状态
  27. //先找到'x'在字符串中所对应的下标
  28. int k=t.find('x');
  29. int x=k/3,y=k%3;//找到'x'的坐标,下一步的操作就是移动'x'的上、下、左、右坐标,
  30. //进而形成新的状态
  31. for(int i=0;i<4;i++)
  32. {
  33. //上下左右的坐标{a,b}
  34. int a=x+dx[i],b=y+dy[i];
  35. if(a>=0 && a<3 && b>=0 && b<3)
  36. {
  37. //'x'所在字符串为t[k],
  38. //二维转换成一维:a*3+b
  39. //交换两种状态(更新当前的状态,使其等于最新的状态)
  40. swap(t[k],t[a*3+b]);
  41. //如果更新完之后的t之前没有被搜过的话,即更新完后的状态之前没出现过的话
  42. /*
  43. 在C++中,std::unordered_map提供了一个成员函数count,该函数用于检查映射中是否存在具有特定键的元素。
  44. 如果映射中存在该键的元素,count函数返回1;如果不存在,返回0。
  45. */
  46. if(!d.count(t))
  47. {
  48. //更新完后的状态到原始状态的距离=之前的距离+1;
  49. d[t]=distance+1;
  50. //新的状态加到队列当中去
  51. q.push(t);
  52. }
  53. //恢复原状,以便下次循环判断新的状态
  54. swap(t[k],t[a*3+b]);
  55. }
  56. }
  57. }
  58. //没找到
  59. return -1;
  60. }
  61. int main()
  62. {
  63. //string表示状态
  64. string start;
  65. for(int i=0;i<9;i++)
  66. {
  67. char c;
  68. cin >> c;
  69. //记录初始状态
  70. start+=c;
  71. }
  72. cout << bfs(start) << endl;
  73. return 0;
  74. }

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])即代表向上

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/995239
推荐阅读
相关标签
  

闽ICP备14008679号