bfs广度优先搜索

BFS广度优先搜索:原理、实现与习题

1.与DFS对比

相对于DFS, BFS的优点是每次求出的都是最短路线,可以用于求最小步数的题目,先来理解一下它是如何搜索的。

请看上图是一颗二叉树,如果我们从点一(根节点)开始出发搜索:

dfs:搜索出每一条路径,走不通退一步

走到二

走到四

退回二

走到五

退回一

走到三

走到六

结束

最后的顺序:124536

bfs:先处理起始点能到的,再处理第1步到的,再处理第2步到的...

先处理一:

        可以走到二和三

处理二:

        可以走到四和五

处理三:

        可以走到六

处理四:

        走不通

处理五:

        走不通

处理六:

        走不通

最后的顺序:123456

如果放在一个图中实现的效果:

*数字代表走的步数零为开始点

2.bfs代码实现

bfs通常使用queue(队列)帮助来实现,题目需要也可以使用数组模拟队列。

先处理起始点能到的,再处理第1步到的,再处理第2步到的...

给出一个图,由零和一构成,给出起点和终点,求最小步数,(保证能走到)

数组准备:

long long v[1004][1005]; //v[x][y]=1走过,v[x][y]=0没走过
long long bs[1004][1005];//到xy的最少步数
long long fx[10] = {1,0,-1,0};//方向数组
long long fy[10] = {0,1,0,-1};

 函数的编写(qx储存的是x坐标,qy储存的是y坐标)

void bfs(){
    queue <int> qx,qy;
    qx.push(sx);//sx,sy是起点
    qy.push(sy);
    while (!qx.empty()){//因为qx,qy同升同降所以判断一个就行
        long long x = qx.front();//取出最前面的元素进行处理
        long long y = qy.front();
        for (int i = 0; i < 4; i++){//往四个方向走看看能不能走到
            long long ex = fx[i]+x;
            long long ey = fy[i] + y;
            if (ex >= 1 and ex <= n and ey >= 1 and ey <= m and a[ex][ey] == 0 and v[ex][ey] == 0){//不超边界不是墙没走过
                qx.push(ex);
                qy.push(ey);//满足条件放进去等待处理
                v[ex][ey] = 1;
                bs[ex][ey] = bs[x][y]+1;//是由XY走过来的
            }
        }
        qx.pop();
        qy.pop();
    }
    
}

 由此代码我们可以得出一个小模板:

void bfs(){
    queue <node> q;//之后的定义可能定义不了多个队列,之后可以用struct进行拓展
    
    while (!q.empty()){
        long long x = q.front();
        q.pop();// pop的放哪都可以
        if (??){
            q.push(?);//如果满足条件放进去一个东西
        }    
    }
    
}

记住这个模板以后可能要用的

3.习题练习

洛古P1135 奇怪的电梯

呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i 层楼(1≤i≤N)上有一个数字 Ki​(0≤Ki​≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 3,3,1,2,5 代表了 Ki​(K1​=3,K2​=3,……),从 1 楼开始。在 1 楼,按“上”可以到 4 楼,按“下”是不起作用的,因为没有 −2 楼。那么,从 A 楼到 B 楼至少要按几次按钮呢?

输入格式

共二行。

第一行为三个用空格隔开的正整数,表示 N,A,B(1≤N≤200,1≤A,B≤N)。

第二行为 N 个用空格隔开的非负整数,表示 Ki​。

输出格式

一行,即最少按键次数,若无法到达,则输出 -1

这道题一看就是BFS是求的是最小按键次数,首先套用模板,考虑在什么条件下入队列,以及考虑node中有什么.

void bfs(){
    queue <node> q;//之后的定义可能定义不了多个队列,之后可以用struct进行拓展
    
    while (!q.empty()){
        long long x = q.front();
        q.pop();// pop的放哪都可以
        if (??){
            q.push(?);//如果满足条件放进去一个东西
        }    
    }
    
}

什么值会变化:步数和楼层 ,所以node里面要储存步数和楼层。

struct A{
    int cnt,id; //id:第几层
}z;

第二个要点就是在什么情况下入队

肯定是分为两种情况:上楼下楼(一加一减) 

算是好算只要判断一下没有出界而且没有走过即可

最后的最后只需要判断出什么时候到达楼层走出

#include <bits/stdc++.h>
using namespace std;
struct A{
    int cnt,id; //id:第几层
}z;
int n,a,b;
int c[100005];
bool v[100005];
int main(){
    cin >> n >> a >> b;
    for (int i = 1; i <= n; i++){
        cin >> c[i];
    } 
    queue <A> q;
    q.push((A){0,a});
    while (!q.empty()){
        z = q.front();
        q.pop();
        if (z.id == b){
            cout << z.cnt;
            return 0;
        }
        if (z.id+c[z.id] <= n and v[z.id+c[z.id]] == 0){
            v[z.id+c[z.id]] = 1;
            q.push((A){z.cnt+1,z.id+c[z.id]});
        }
        if (z.id-c[z.id] >= 1 and v[z.id-c[z.id]] == 0){
            v[z.id-c[z.id]] = 1;
            q.push((A){z.cnt+1,z.id-c[z.id]});
        }
    }
    cout << "-1";
    return 0;
}

P1141 01迷宫

有一个仅由数字 0 与 1 组成的 n×n 格迷宫。若你位于一格 0 上,那么你可以移动到相邻 4 格中的某一格 1 上,同样若你位于一格 1 上,那么你可以移动到相邻 4 格中的某一格 0 上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入格式

第一行为两个正整数 n,m。

下面 n 行,每行 n 个字符,字符只可能是 0 或者 1,字符之间没有空格。

接下来 m 行,每行两个用空格分隔的正整数 i,j,对应了迷宫中第 i 行第 j 列的一个格子,询问从这一格开始能移动到多少格。

输出格式

m 行,对于每个询问输出相应答案。

  • 对于 20% 的数据,n≤10;
  • 对于 40% 的数据,n≤50;
  • 对于 50% 的数据,m≤5;
  • 对于 60% 的数据,n,m≤100;
  • 对于 100% 的数据,1≤n≤1000,1≤m≤100000。

这道题看上去是需要模拟出一步步走,可是看到数据范围的时候发现可能会超时而且有m组样例。

而且题目中并没有出现最少次数,乍一看好像不能用BFS做。

看我加粗的字样你会发现那是在求一个连通块,毕竟如果我能走到这个点那个点也能走到我这个点上。

BFS也能求联通块吗?然后我们来试一试吧!

BFS2部曲:存什么,怎样才存

首先思考一下队列里存什么,毫无疑问就是存xy坐标

怎样才存:这个地方没走过而且满足零一的条件而且在边框内

那么就开写吧!

int fx[10] = {0,0,-1,1};
int fy[10] = {1,-1,0,0};
int a[1005][1005],f[1005][1005]/*f[x][y]=L L是联通块的名字序号*/,c[1000004]/*c[L] = cnt,L有cnt个格子*/,n;

一些数组

void bfs(int sx,int sy,int s){ //找S联通快
    queue <int> qx,qy;
    qx.push(sx);
    qy.push(sy);
    while (!qx.empty()){
        long long x = qx.front();
        long long y = qy.front();
        qx.pop();
        qy.pop();
        if (f[x][y]){//原来有值不搜索
            continue;
        }
        f[x][y] = s;//它属于S的
        c[s]++;
        int be = 0;//当我们走到那个位置那个位置需要是be才能走
        if (a[x][y] == 0){
            be = 1;
        }
    }
}

再加上最重要的怎样才能给它放入队列的部分 

void bfs(int sx,int sy,int s){
    queue <int> qx,qy;
    qx.push(sx);
    qy.push(sy);
    while (!qx.empty()){
        long long x = qx.front();
        long long y = qy.front();
        qx.pop();
        qy.pop();
        if (f[x][y]){
            continue;
        }
        f[x][y] = s;
        c[s]++;
        int be = 0;
        if (a[x][y] == 0){
            be = 1;
        }
        for (int i = 0; i < 4; i++){
            long long ex = fx[i]+x;
            long long ey = fy[i] + y;
            if (ex >= 1 and ex <= n and ey >= 1 and ey <= n and a[ex][ey] == be and  f[ex][ey] == 0){
                qx.push(ex);
                qy.push(ey);
            }
        }
    }
}

当然BFS写完了这道题并没有结束,只是求的是S联通块,在询问中如果遇到还没有赋值的,就跑一下BFS,如果已经被赋值看一眼这个连通块有多少个输出就行

整体代码:

#include <bits/stdc++.h>
using namespace std;
int fx[10] = {0,0,-1,1};
int fy[10] = {1,-1,0,0};
int a[1005][1005],f[1005][1005]/*f[x][y]=L L是联通块的名字序号*/,c[1000004]/*c[L] = cnt,L有cnt个格子*/,n;
void bfs(int sx,int sy,int s){
    queue <int> qx,qy;
    qx.push(sx);
    qy.push(sy);
    while (!qx.empty()){
        long long x = qx.front();
        long long y = qy.front();
        qx.pop();
        qy.pop();
        if (f[x][y]){
            continue;
        }
        f[x][y] = s;
        c[s]++;
        int be = 0;
        if (a[x][y] == 0){
            be = 1;
        }
        for (int i = 0; i < 4; i++){
            long long ex = fx[i]+x;
            long long ey = fy[i] + y;
            if (ex >= 1 and ex <= n and ey >= 1 and ey <= n and a[ex][ey] == be and  f[ex][ey] == 0){
                qx.push(ex);
                qy.push(ey);
            }
        }
    }
}
int main(){
    int m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        string s;
        cin >> s;
        for (int j = 1; j <= n; j++){
            a[i][j] = s[j-1]-'0';
        }
    }
    for (int i = 1; i <= m; i++){
        int x,y;
        cin >> x >> y;
        if (f[x][y] == 0){ //如果还没有被定义是哪个连通块的
            bfs(x,y,i); //全部的都找出来
        }
        else{
            c[i] = c[f[x][y]]; //他自己的个数等于它们连通块的个数
        }
    }
    
    for (int i = 1; i <= m; i++){
        cout << c[i] << "\n";
    }
    return 0;
}

P1126 机器人搬重物

机器人移动学会(RMI)现在正尝试用机器人搬运物品。机器人的形状是一个直径 1.6 米的球。在试验阶段,机器人被用于在一个储藏室中搬运货物。储藏室是一个 N×M 的网格,有些格子为不可移动的障碍。机器人的中心总是在格点上,当然,机器人必须在最短的时间内把物品搬运到指定的地方。机器人接受的指令有:

  • 向前移动 1 步(Creep);
  • 向前移动 2 步(Walk);
  • 向前移动 3 步(Run);
  • 向左转(Left);
  • 向右转(Right)。

每个指令所需要的时间为 1 秒。请你计算一下机器人完成任务所需的最少时间。

输入格式

第一行为两个正整数 N,M (1≤N,M≤50),下面 N 行是储藏室的构造,0 表示无障碍,1 表示有障碍,数字之间用一个空格隔开。接着一行有 4 个整数和 1 个大写字母,分别为起始点和目标点左上角网格的行与列,起始时的面对方向(东 E,南 S,西 W,北 N),数与数,数与字母之间均用一个空格隔开。终点的面向方向是任意的。

输出格式

一个整数,表示机器人完成任务所需的最少时间。如果无法到达,输出 −1。

输入 #1

9 10
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 1 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 1 0
7 2 2 7 S

输出 #1

12

这道题虽说是绿题,但是难点只是处理数组, BFS用我的方法写一点都不难

这道题的题目有一个坑,障碍物是方形的,而机器人是走直线的,就跟题目配的图一样,但是并不是可以随意走,因为机器人本身有一个宽度,所以障碍物的四周是不能走的.

相当于把地图压缩一下,在输入的时候处理一下如果这一各位有障碍物那把它四周都弄上障碍物这样就变成一个点子图了

for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            cin >> a[i][j];
            if (a[i][j]){
                a[i-1][j-1] = 1;
                a[i-1][j] = 1;
                a[i][j-1] = 1;
            }
        }
    }

 继续施行两部曲:存什么,怎样才存

很明显什么在程序运行中会改变,肯定就是xy坐标还有方向和步数


struct node{
    int x,y,f,s;//x,y,方向,步数
};

v数组也要加一维步数的存在了

 首先来处理直走

        for (int i = 1; i <= 3; i++){//可以走1、2、3步
            int ex = nq.x + fx[nq.f]*i,ey = nq.y + fy[nq.f]*i;
            if (a[ex][ey] == 1 or ex < 1 or ey <1 or ex >= n or ey >= m){//超边界了或者有障碍物
                break;
            }
            if (v[ex][ey][nq.f]){//之前这个地方这个方向来过
                continue;
            }
            v[ex][ey][nq.f] = 1;//符合条件 就走  
            q.push({ex,ey,nq.f,nq.s+1});
            
        }

处理转向, 我用了两个数组(下面两个处理转向):

int fx[10] = {0,-1,1,0,0};//自己,上1,下2,左3,右4
int fy[10] = {0,0,0,-1,1};
int l[10] = {0,3,4,2,1};//l[i] = j,i向左转到j
int r[10] = {0,4,3,1,2};//r[i] = j,i向右转到j
int lf = l[nq.f];//向左转  
if (!v[nq.x][nq.y][lf]){//之前这个地方这个方向没来过
    v[nq.x][nq.y][lf] = 1;//符合条件 就走  
    q.push({nq.x,nq.y,lf,nq.s+1});
}
int rf = r[nq.f];//向右转  
if (!v[nq.x][nq.y][rf]){//之前这个地方这个方向没来过
    v[nq.x][nq.y][rf] = 1;//符合条件 就走  
    q.push({nq.x,nq.y,rf,nq.s+1});
}

接下来就是整体BFS的展示:

void bfs(){
    queue <node> q;
    q.push({sx,sy,sf,0});
    v[sx][sy][sf] = 1;
    
    while (!q.empty()){
        node nq = q.front();//new q
        q.pop();
        if (nq.x == tx and nq.y == ty){
            cout << nq.s;
            return ;
        }
        for (int i = 1; i <= 3; i++){//可以走1、2、3步
            int ex = nq.x + fx[nq.f]*i,ey = nq.y + fy[nq.f]*i;
            if (a[ex][ey] == 1 or ex < 1 or ey <1 or ex >= n or ey >= m){//超边界了或者有障碍物
                break;
            }
            if (v[ex][ey][nq.f]){//之前这个地方这个方向来过
                continue;
            }
            v[ex][ey][nq.f] = 1;//符合条件 就走  
            q.push({ex,ey,nq.f,nq.s+1});
            
        }
        //也可以选择转向  
        int lf = l[nq.f];//向左转  
        if (!v[nq.x][nq.y][lf]){//之前这个地方这个方向没来过
            v[nq.x][nq.y][lf] = 1;//符合条件 就走  
            q.push({nq.x,nq.y,lf,nq.s+1});
        }
        int rf = r[nq.f];//向右转  
        if (!v[nq.x][nq.y][rf]){//之前这个地方这个方向没来过
            v[nq.x][nq.y][rf] = 1;//符合条件 就走  
            q.push({nq.x,nq.y,rf,nq.s+1});
        }
    }
    cout << -1;
}

 整体代码加上主函数的:
 


#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[55][55];
int sx,sy,tx,ty,sf/*一开始朝向转换成数字*/;
char face;
int v[55][55][5];
int fx[10] = {0,-1,1,0,0};//自己,上1,下2,左3,右4
int fy[10] = {0,0,0,-1,1};
int l[10] = {0,3,4,2,1};//l[i] = j,i向左转到j
int r[10] = {0,4,3,1,2};//r[i] = j,i向右转到j
struct node{
    int x,y,f,s;//x,y,方向,步数
};
void bfs(){
    queue <node> q;
    q.push({sx,sy,sf,0});
    v[sx][sy][sf] = 1;
    
    while (!q.empty()){
        node nq = q.front();//new q
        q.pop();
        if (nq.x == tx and nq.y == ty){
            cout << nq.s;
            return ;
        }
        for (int i = 1; i <= 3; i++){//可以走1、2、3步
            int ex = nq.x + fx[nq.f]*i,ey = nq.y + fy[nq.f]*i;
            if (a[ex][ey] == 1 or ex < 1 or ey <1 or ex >= n or ey >= m){//超边界了或者有障碍物
                break;
            }
            if (v[ex][ey][nq.f]){//之前这个地方这个方向来过
                continue;
            }
            v[ex][ey][nq.f] = 1;//符合条件 就走  
            q.push({ex,ey,nq.f,nq.s+1});
            
        }
        //也可以选择转向  
        int lf = l[nq.f];//向左转  
        if (!v[nq.x][nq.y][lf]){//之前这个地方这个方向没来过
            v[nq.x][nq.y][lf] = 1;//符合条件 就走  
            q.push({nq.x,nq.y,lf,nq.s+1});
        }
        int rf = r[nq.f];//向右转  
        if (!v[nq.x][nq.y][rf]){//之前这个地方这个方向没来过
            v[nq.x][nq.y][rf] = 1;//符合条件 就走  
            q.push({nq.x,nq.y,rf,nq.s+1});
        }
    }
    cout << -1;
}
int main(){
    long long xx,yy;
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            cin >> a[i][j];
            if (a[i][j]){
                a[i-1][j-1] = 1;
                a[i-1][j] = 1;
                a[i][j-1] = 1;
            }
        }
    }
    cin >> sx >> sy >> tx >> ty >> face;
    if (face == 'N'){
        sf=1;//上
    }if (face == 'S'){
        sf=2;//下
    }if (face == 'W'){
        sf=3;//左
    }if (face == 'E'){
        sf=4;//右
    }
    bfs();
    return 0;
}

 4.最后总结

BFS小模板:

void bfs(){
    queue <node> q;//之后的定义可能定义不了多个队列,之后可以用struct进行拓展
    
    while (!q.empty()){
        long long x = q.front();
        q.pop();// pop的放哪都可以
        if (??){
            q.push(?);//如果满足条件放进去一个东西
        }    
    }
    
}

BFS两部曲:存什么,怎样才存 

理解下来你会觉得BFS其实没有那么难

<think>好的,我需要回答用户关于BFS广度优先搜索的示例代码和算法实现的问题。首先,我要确保理解用户的需求:他们希望看到一个具体的BFS实例,包括代码实现和步骤说明,并且可能需要了解BFS的应用场景。用户还提到参考了三个引用资料,其中引用了图遍历、算法原理和BFS的基本思想,这些需要整合到回答中。 首先,我应该回顾BFS的基本原理,确保正确性。根据引用[1]和[2],BFS是按层次逐层访问节点,通常用队列实现,适用于寻找最短路径和无权图的遍历。引用[3]提到BFS适用于无向图和有向图。 接下来,我需要选择一个合适的例子。常见的例子是图的遍历,比如社交网络中的层级关系或者迷宫问题。这里可能用图的邻接表表示,然后展示代码。用户需要Python代码,所以我会用Python编写示例。 然后,编写代码时需要注意队列的使用,以及访问节点的标记,避免重复访问。代码结构应该清晰,包含队列初始化、循环处理节点、遍历邻居节点等步骤。同时,需要注释解释每一步的作用。 之后,说明算法步骤,分点解释每个部分的作用,比如初始化队列、标记已访问节点、处理当前节点、遍历邻居等。这部分需要结合引用中的原理,如逐层扩展、最短路径应用等。 还要考虑相关问题的生成,用户可能需要了解BFS与DFS的区别、时间复杂度,或者应用场景如最短路径、社交网络等。根据引用内容,可以提出相关问题,比如BFS在哪些场景下更优,如何优化BFS,或者与DFS的比较。 需要确保回答符合系统级指令:行内数学公式用$...$,独立公式用$$...$$,但BFS可能不涉及太多数学公式,可能不需要。但如果有,比如层数计算,可能需要用到。不过当前例子可能不涉及,所以暂时忽略。 最后,检查引用标注是否正确,每个引用在回答中的引用位置是否自然,例如在提到BFS的基本原理或应用场景时引用对应的资料。例如,当说明BFS逐层访问时,引用[2];提到图遍历时引用[3]。 总结步骤: 1. 介绍BFS的基本原理,引用相关文献。 2. 提供一个图的示例,用邻接表表示。 3. 展示Python代码,详细注释。 4. 分步解释算法流程。 5. 列举应用场景,如最短路径、社交网络。 6. 生成相关问题,如与DFS对比、时间复杂度、优化方法等。 7. 确保所有引用标注正确,格式符合要求。</think>### BFS广度优先搜索算法实现与示例代码 #### 1. BFS基本原理 广度优先搜索BFS)是一种按层次遍历图或树的算法。从起始节点开始,逐层访问相邻节点,确保先访问所有距离为$k$的节点后再处理距离为$k+1$的节点[^2]。这种特性使其在寻找最短路径、判断图连通性等场景中非常高效[^1][^3]。 --- #### 2. BFS示例代码(Python实现) 以下是一个以无向图为例的BFS实现,目标是遍历所有节点并输出访问顺序: ```python from collections import deque def bfs(graph, start): visited = set() # 记录已访问的节点 queue = deque([start]) # 使用队列存储待访问节点 visited.add(start) while queue: node = queue.popleft() print(node, end=" ") # 输出当前节点 # 遍历当前节点的所有邻居 for neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) # 示例图(邻接表表示) graph = { 'A': ['B', 'C'], 'B': ['A', 'D', 'E'], 'C': ['A', 'F'], 'D': ['B'], 'E': ['B', 'F'], 'F': ['C', 'E'] } # 从节点'A'开始遍历 print("BFS遍历顺序:") bfs(graph, 'A') # 输出: A B C D E F ``` --- #### 3. 算法步骤解析 1. **初始化队列和访问标记** - 使用队列`deque`存储待访问节点,起始节点`A`入队。 - `visited`集合标记已访问节点,避免重复处理。 2. **逐层处理节点** - 从队列头部取出节点(如`A`),输出并遍历其邻居(`B`和`C`)。 - 将未访问的邻居(`B`、`C`)加入队列尾部,并标记为已访问。 3. **重复直到队列为空** - 队列变为空时,说明所有可达节点均已访问。 --- #### 4. BFS典型应用场景 - **最短路径问题**:在无权图中,BFS首次访问到目标节点的路径即为最短路径。 - **社交网络层级分析**:如查找用户之间的最短关系链。 - **连通性判断**:检测图中是否存在从起点到终点的路径[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值