BFS 广度优先搜索 ( Breadth-First-Search )

BFS 广度优先搜索 ( Breadth-First-Search )

广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索", BFS是用于图的查找算法(要求能用图表示出问题的关联性)。

BFS可用于解决2类问题:

1.从A出发是否存在到达B的路径;DFS也可求

2.从A出发到达B的最短路径;数据小20以内的话, DFS也不是不可以 题眼

整体思路

其思路为从图上一个节点出发,访问先访问其直接相连的子节点,若子节点不符合,再问其子节点的子节点,按级别顺序(一层一层)依次访问,直到访问到目标节点。

步骤

  • 起始:将起点(源点,树的根节点)放入队列中
  • 扩散:从队列中取出队头的结点,将它的相邻结点放入队列,不断重复这一步
  • 终止:当队列为空时,说明我们遍历了所有的能到的结点,整个图能到的点都被搜索了一遍

时空复杂度

时间复杂度 : 最差情形下,BFS必须寻找所有到可能节点的所有路径,因此其时间复杂度为O(|V| + |E|),其中|V|是节点的数目,而|E|是图中边的数目。

空间复杂度 : BFS的空间复杂度为 O(B^h),其中B是最大分支系数,而h是树的最长路径长度(树的高度) 。由于对空间的大量需求,因此BFS并不适合解非常大的问题。

对于所有边长度相同的情况,比如地图的模型,bfs第一次遇到目标点,此时就一定是从根节点到目标节点最短的路径(因为每一次所有点都是向外扩张一步,你先遇到,那你就一定最短)。bfs找到的一定是最短的。但是如果是加权边的话这样就会出问题了,bfs传回的是经过边数最少的解,但是因为加权了,这个解到根节点的距离不一定最短。比如1000+1000是只有两段,1+1+1+1有4段,由于bfs返回的经过边数最少的解,这里会返回总长度2000的那个解,显然不是距离最短的路径。此时我们就应该采用Dijkstra最短路算法解决加权路径的最短路了。

SPFA

例题

走迷宫

acw844. 走迷宫 , 若打不开这个题就看这个 P1746 离开中山路

//比赛中可以直接用万能头文件
#include<iostream>
#include<string>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#include<cstring>
//开longlong防止爆掉int,int范围2e9,longlong范围9e18
#define int long long //(有超时风险)
//简写,如果要改动PII,直接该这个就行了,vector常用
#define PII pair<int,int>


#define x first
#define y second
using namespace std;

//需要改动数组直接改动N,M即可
const int N=110,M=1e3+10;

//常用的数组
int a[N],pre[N];
int n,m;
int g[N][N];//存地图
int dist[N][N];//存每个点到起点的距离
queue<PII>q;//队列

int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};


int dfs(int x1,int y1)
{
    memset(dist,-1,sizeof dist);//距离都初始化为-1
    q.push({x1,y1});
    dist[x1][y1]=0;

    while (q.size())//队列不空,继续循环
    {
        auto t=q.front();//取出队头
        q.pop();//弹出
        for(int i=0;i<4;i++)
        {
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a<1||a>n||b<1||b>m)continue;
            if(g[a][b]!=0)continue;
            if(dist[a][b]>0)continue;
            q.push({a,b});
            dist[a][b]=dist[t.x][t.y]+1;
        }
    }
    return dist[n][m];
}

signed main()
{
    //关掉流同步,cin变快,但是不能用scanf,可以用printf;
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>g[i][j];
    int res=dfs(1,1);
    cout<<res<<endl;
    return 0;
}


习题 - 简单迷宫问题

离开中山路

P1746 离开中山路 1

//比赛中可以直接用万能头文件
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
//开longlong防止爆掉int,int范围2e9,longlong范围9e18
#define int long long //(有超时风险)
//简写,如果要改动PII,直接该这个就行了,vector常用
#define PII pair<int,int>
//'\n'输出更快一些,而且是分别显示每一行,建议scanf与printf配套
#define endl '\n'

#define x first
#define y second

using namespace std;

//需要改动数组直接改动N,M即可
const int N=1010,M=1e3+10;
int n;
int dist[N][N];
queue<PII>q;
char g[N][N];
int x1,y1,x2,y2;

//常用的数组
int a[N],pre[N];
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};

int bfs(int x1,int y1)
{
    memset(dist,-1, sizeof dist);
    q.push({x1,y1});
    dist[x1][y1]=0;
    while (q.size())
    {
        auto t=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a<1||a>n||b<1||b>n)continue;
            if(g[a][b]!='0')continue;
            if(dist[a][b]>=0)continue;//表示走过了,不能重复走

            q.push({a,b});
            dist[a][b]=dist[t.x][t.y]+1;
            if(dist[x2][y2]>0)return dist[x2][y2];
        }
    }
    return -1;
}

signed main()
{
    //关掉流同步,cin变快,但是不能用scanf,可以用printf;
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>g[i][j];
    cin>>x1>>y1>>x2>>y2;
    int res=bfs(x1,y1);
    cout<<res<<endl;
    return 0;
}

马的遍历

P1443 马的遍历 1
用数组模拟队列

//比赛中可以直接用万能头文件
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
//开longlong防止爆掉int,int范围2e9,longlong范围9e18
#define int long long //(有超时风险)
//简写,如果要改动PII,直接该这个就行了,vector常用
#define PII pair<int,int>
#define x first
#define y second

using namespace std;

//需要改动数组直接改动N,M即可
const int N=410,M=1e3+10;

//常用的数组
int a[N],pre[N];
int n,m;
int dist[N][N];
PII q[N*N];
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};


void bfs(int x1,int y1)
{
    memset(dist,-1, sizeof(dist));
    q[0]={x1,y1};
    dist[x1][y1]=0;
    int hh=0,tt=0;//tt本来应该等于-1,因为上面已经加入了一个元素,所以tt=0

    while (hh<=tt)
    {
        //auto t=q[hh]; 取出队头 t=q.front();
        //hh++;    q.pop();
        auto t=q[hh++];
        for(int i=0;i<8;i++)
        {
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a<1||a>n||b<1||b>m)continue;
            if(dist[a][b]>=0)continue;//表示已经走过了,不能重复走

            dist[a][b]=dist[t.x][t.y]+1;
            q[++tt]={a,b};
        }
    }
}

signed main()
{
    
    int x1,y1;
    cin>>n>>m>>x1>>y1;
    bfs(x1,y1);
    for(int i=1;i<=n;i++) {
        for (int j = 1; j <= m; j++)
            printf("%-5d",dist[i][j]);
        cout << endl;
    }

    return 0;
}

好奇怪的游戏

--------------P1747 好奇怪的游戏 (跑2次) 0

Bronze Lilypad Pond B

--------------P2385 Bronze Lilypad Pond B 可控马步

多源BFS

从多个起点同时开始

血色先锋队

P1332 血色先锋队 1

//比赛中可以直接用万能头文件
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
//开longlong防止爆掉int,int范围2e9,longlong范围9e18
#define int long long //(有超时风险)
//简写,如果要改动PII,直接该这个就行了,vector常用
#define PII pair<int,int>
#define x first
#define y second

using namespace std;

//需要改动数组直接改动N,M即可
const int N=510,M=1e3+10;

//常用的数组
int n,m,a,b;
int dist[N][N];
PII q[N*N];
int hh=0,tt=-1;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};


void bfs()
{
    while (hh<=tt)
    {
        auto t=q[hh++];
        for(int i=0;i<4;i++)
        {
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a<1||a>n||b<1||b>m)continue;
            if(dist[a][b]>=0)continue;
            dist[a][b]=dist[t.x][t.y]+1;
            q[++tt]={a,b};
        }
    }
}

signed main()
{
    //关掉流同步,cin变快,但是不能用scanf,可以用printf;
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>a>>b;
    memset(dist,-1,sizeof dist);
    while (a--)
    {
        int x1,y1;
        cin>>x1>>y1;
        q[++tt]={x1,y1};
        dist[x1][y1]=0;
    }
    bfs();
    //bfs扫完直接输入领主的位置,直接输出他在第几轮被感染
    while (b--)
    {
        int x1,y1;
        cin>>x1>>y1;
        cout<<dist[x1][y1]<<endl;
    }
    return 0;
}

或者用这种方式存领主
在这里插入图片描述

矩阵距离

--------------------acw173.矩阵距离 0

染色问题

填涂颜色

P1162 填涂颜色9 1

//比赛中可以直接用万能头文件
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
//开longlong防止爆掉int,int范围2e9,longlong范围9e18
#define int long long //(有超时风险)
//简写,如果要改动PII,直接该这个就行了,vector常用
#define PII pair<int,int>
#define x first
#define y second

using namespace std;

//需要改动数组直接改动N,M即可
const int N=35,M=1e3+10;
int n;
int g[N][N];
bool st[N][N];
PII q[N*N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};


void bfs(int x1,int y1)
{
    q[0]={x1,y1};
    st[x1][y1]=true;
    int hh=0,tt=0;

    while (hh<=tt)
    {
        auto t=q[hh++];//取出队头
        for(int i=0;i<4;i++)
        {
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a<0||a>n+1||b<0||b>n+1)continue;
            if(g[a][b]==1)continue;
            if(st[a][b])continue;

            st[a][b]= true;
            q[++tt]={a,b};
        }
    }
    return;
}

signed main()
{
    //关掉流同步,cin变快,但是不能用scanf,可以用printf;
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>g[i][j];
    bfs(0,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(g[i][j]==0&&!st[i][j])
                g[i][j]=2;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++) {
            cout << g[i][j] << " ";
        }
        cout<<endl;
    }
    return 0;
}
//bfs后重新遍历一遍,如果这个点等于0,并且没有被标记过,就把这个点变成2,
//因为不能走1,所以没被标记的,都是需要染色的
//在圈外加一圈0,避免(1,1)是在1上,周围都是1,无法移动,从(0,0)开始,到(n+1,n+1)

在这里插入图片描述

拯救oibh总部

-----------------------P1506 拯救oibh总部

Forgery

-----------------------CF1059B Forgery

有外界干扰的迷宫问题

Meteor Shower S

P2895 Meteor Shower S 天降陨石 1

//比赛中可以直接用万能头文件
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
//开longlong防止爆掉int,int范围2e9,longlong范围9e18
#define int long long //(有超时风险)
//简写,如果要改动PII,直接该这个就行了,vector常用
#define PII pair<int,int>
#define x first
#define y second

using namespace std;

//需要改动数组直接改动N,M即可
const int N=310,M=1e3+10;

int m;
int dist[N][N];
int fire[N][N];//记录流星砸到(i,j)的时间
PII q[N*N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int bfs()
{
    q[0]={0,0};
    dist[0][0]=0;
    int hh=0,tt=0;
    while(hh<=tt)
    {
        auto t=q[hh++];
        for(int i=0;i<4;i++)
        {
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a<0||b<0)continue;
            if(dist[a][b])continue;
            if(dist[t.x][t.y]+1>=fire[a][b])continue;

            dist[a][b]=dist[t.x][t.y]+1;
            q[++tt]={a,b};
            if(fire[a][b]>1e9)return dist[a][b];
        }
    }
    return -1;
}

signed main()
{
    //关掉流同步,cin变快,但是不能用scanf,可以用printf;
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>m;
    memset(fire,0x3f,sizeof fire);
    while (m--)
    {
        int x1,y1,t;
        cin>>x1>>y1>>t;
        fire[x1][y1]=min(t,fire[x1][y1]);
        for(int i=0;i<4;i++)
        {
            int a=x1+dx[i],b=y1+dy[i];
            if(a<0||a>301||b<0||b>301)continue;//因为流星在300,他的焦土在301
            fire[a][b]=min(t,fire[a][b]);
        }
    }
    int res=bfs();
    cout<<res<<endl;

    return 0;
}
//先对流星砸过的土地预处理,把流星砸过来的时间标记在地图上

在这里插入图片描述

路障

--------------------P3395 路障 天降路障 0.5

看起来比较困难的BFS

汽车拉力比赛

P2658 汽车拉力比赛 1

机器人搬重物

--------------P1126 机器人搬重物

魔板 Magic Squares

P2730 魔板 Magic Squares

双端队列广搜

小明的游戏

P4554 小明的游戏 1

棋盘

--------------------------P3596 棋盘

Switch the Lamp On

--------------------------P4667 Switch the Lamp On

双向广搜

离开中山路

P1746 离开中山路 1

八数码难题

P1379 八数码难题 1

献给阿尔吉侬的花束

------------acw1101.献给阿尔吉侬的花束

骑士的移动

------------UVA439 骑士的移动

奇怪的电梯

------------P1135 奇怪的电梯

字串变换

------------P1032 字串变换

Corn Maze S

------------P1825 Corn Maze S

------------more and more…知道起点和终点状态的都可以用双向BFS来优化时间


作业 (上节课DFS过不去, 这节课可以用BFS试一试~)

奇怪的电梯

P1135 奇怪的电梯 dfs暴力过不去

Flood fill — 找连通块

Lake Counting S

P1596 Lake Counting S

求细胞数量

P1451求细胞数量

海战

P1331 海战

家族

P1767 家族

UCV2013H - Slick

SP15436 UCV2013H - Slick

迷宫问题

入门

P1683 入门

迷宫

P1605 迷宫

马的遍历

P1443 马的遍历

好奇怪的游戏

P1747 好奇怪的游戏

Mzc和男家丁的游戏

P2298 Mzc和男家丁的游戏

关于memset和0x3f

int a[100];

memset(a,0x3f,sizeof(a) );

0x3f=0011 1111=63
C++中int型变量所占的位数为4个字节,即32位
0x3f显然不是int型变量中单个字节的最大值,应该是0x7f=0111 1111 B
那为什么要赋值0x3f ??

int

1.作为无穷大使用

因为4个字节均为0x3f时,0x3f3f3f3f的十进制是1061109567,也就是10^ 9级别的(和0x7fffffff一个数量级),而一般场合下的数据都是小于10^9的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。

2.可以保证无穷大加无穷大仍然不会超限。

另一方面,由于一般的数据都不会大于10^9,所以当我们把无穷大加上一个数据时,它并不会溢出(这就满足了**“无穷大加一个有穷的数依然是无穷大”),事实上0x3f3f3f3f+0x3f3f3f3f=2122219134,这非常大但却没有超过32-bit int_MAX的表示范围,所以0x3f3f3f3f还满足了我们“无穷大加无穷大还是无穷大”的需求**。

首先要知道memset函数是以字节为单位进行赋值的;

void memset(void *s, int ch, size_t n);

函数解释:将s中前n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
其实这里面的ch就是ascii为ch的字符;

将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值

为什么需要队列

正如以上所说,我们需要一层一层去遍历到所有结点,那么相邻结点的访问顺序如何确定?因此我们就需要一个数据结构去存储和操作,需要使得先遍历到的结点先被存储,直到当前层都被存储之后,按照存储的先后顺序,先被存储的结点也会被先取出来继续遍历他的子节点—>综上所述,需要一种特点为先进先出的数据结构也就是队列! Queue!
queue的特点是: 先进先出(first in first out — FIFO)

求最短路径用BFS

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值