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其实没有那么难

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值