比较基础的广度优先搜索算法!!!

公告:在2020/3/24 0:20之前把最短路模板拿走的同志们,看这里!

如果你回来了请仔细阅读公告!

真的对不起大家!是我误人子弟了,我在2020/3/24 0:20前发布的博客所给出的最短路模板是错误的。
因为我是临时拿一个一维的广搜改的,所以好多地方数组都是写的一维的,没有改过来。

在之前的拿走模板的同志们真的抱歉了。我以后一定检查好自己的文章再发布,不再犯这样的错误了

 

 

广度优先搜索算法的定义!!!

憋了四天了,本蒟蒻终于学会了广搜,即刻与大家分享学习经历!

先上广搜的定义!

宽度优先搜索算法(又称广度优先搜索)是最简便的图论的搜索算法之一,这一算法也是很多重要的图论算法的原型。

Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。

其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。

换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

——来自百度百科

个人的理解

百度百科的解释一塌糊涂,上面说的好多知识我也不会,怕各位弄不明白,(瞎JB操心)就写一个我个人对广搜的理解。

广度优先搜索算法,与深度优先搜索算法不同的是,他从一个节点出发,会优先遍历这一个点的所有子节点,

然后再从这个点的第一个子节点遍历所有子节点,再是第二个……第三个……(废话)

下面举个栗子:

就像一个庞大的家族,我们要查家族中某个人的户口,就先从祖宗查起,先找祖宗有几个孩子。

如果你要找的人不是祖宗的孩子,那就从老大的第一个孩子……第二个孩子……,老二的第一个孩子……第二个孩子,

一直循环往复,一定可以返回一个值(你要查的那个人)。

既然名字那么像,广搜和深搜各自有什么特点和不同呢?

其实这俩哥们虽然都叫搜索,但是实现方法完全不同,深搜是依靠递归,广搜是依靠队列。

这也导致了双方特点和应用上的不同。

深搜呢,最大的优势就是——不!占!空!间!

写起来也简单,深搜有回溯算法,如果在这一棵子树中没有得到结果,就去另一棵。

深搜被广泛用于限定条件求方法种数的问题,比如八皇后问题,显然广搜对每个节点只遍历一次的方法是不能解决问题的。

深搜也有很明显的缺点,容易TLE,大家每次写暴力的时候都会与这个TLE作斗争,运行效率低也是深搜的一大短板。

广搜呢,优势在于寻找最短路!

广搜与深搜不同的是,整张图的每个子节点都只会被搜到一次,每个节点总是以最短路径的形式被访问。

所以广搜对于寻找最短路来说有奇效。

因为广搜对于每个子节点只会搜索一次,所以程序运行效率也会提升。(当然深搜可以优化复杂度,比如记忆化、完全剪枝这些,不多BB)

缺点也很明显,因为广搜依赖队列,如果子节点太多,会爆系统栈,所以可能会MLE。

对于某一些问题,深搜和广搜都能胜任。

因为都是枚举所有可能,所以这两个都只适用于数据规模较小的情况。

广搜怎么实现呢?

我的上一篇博客已经提到了队列,广搜就是依靠队列来实现。

具体请看图:这是具体的思路了

 

广搜的应用之一——寻找最短路。

找最短路是一类模板感特别强的题,所以套模板可以应付大多数的最短路问题!!!(一个蒟蒻瞎比比啥)

下面我提供一个最短路模板,不一定是最好的,仅供参考。

 

#include<iostream>
#include<queue>//STL队列头文件 
using namespace std;
int mapp[110][110];//小地图,记录路径用 
int step[110][110];//记录步数用的 
int go[5][2]{{0,0},{1,0},{-1,0},{0,1},{0,-1}};//定义可以走的方向 
struct STU{//定义一个结构体,包括横纵坐标 
    int xx;
    int yy;
};
queue<STU>q;//定义一个结构体类型的队列 
int n,m,sx,sy,ex,ey,qx,qy,tx,ty;//n,m是题目要求的地图边界
//我个人建议固定自己喜欢的变量名,建立模板化的体系,这样写最短路的时候会更加井井有条! 
int bfs(int x,int y)//x,y是初始结点
{
    q.push((STU){x,y});//坐标入队
    mapp[x][y]=1;//标记路径已经走过 
    while(q.size()!=0)//当队列不为空的时候,说明还没遍历完 
    {
        qx=q.front().xx;//qx,qy为当前坐标
        qy=q.front().yy;
        q.pop();//弹出初始坐标,进行对该坐标的搜索
        if(qx==ex&&qy==ey)//如果qx,qy的数值满足终止条件,停止搜索并返回值
        {
            return step[qx][qy];//返回计步数组的值
        }
        for(int i=1;i<=4;i++)//枚举方向(对于可以向左右的数轴问题,对于别的你可以看情况枚举路径可能)
        {
            tx=qx+go[i][0];//go是方向数组,tx,ty是向这个方向走后的坐标 
            ty=qy+go[i][1];           
            if(1>tx||tx>m||1>ty||ty>n)//我个人习惯,地图是从(1,1)开始 
            {
                continue;
            }
            if(mapp[tx][ty]==1)//如果超出边界或者已经被搜过,即视为不合法,continue掉
            {
                continue;
            }
            step[tx][ty]=step[qx][qy]+1;//如果合法,计步数组的值为上一步+1 
            mapp[tx][ty]=1;//记录路径,避免重复搜索 
            q.push((STU){tx,ty});//把tx,ty入队 
        }
    }
    return -1;//如果没搜到结果,返回-1 
}

 

 

 

 核心就是while里的弹出,判断和入队操作。

 如果您们如果觉得广搜难,可以慢慢体会。简单的题目前只有求最短路非用广搜不可。其他深搜都可以胜任(如果您深搜也不太会的话,我如果过几天闲的没事会更新深搜博客,大概吧)

到了后期,广搜就很重要了,图论算法的常客。所以一定要扎实!(瞎操心*2)

行了,理论知识都齐备了,下面看看几个最短路的题(都是一本通上的)

真题实战

ybt1249(该题可以深搜替代!!!)

(因为之前复制题目的时候出了点bug,这里放图片)

 我先来BB两句思路!

首先,题目中说W是水坑,然后每一个水坑的八个方向中如果有另一个水坑,就视为一个大水坑。

最后问给出的矩阵中有几个水坑。

这个题和那个细胞的例题大同小异。

我们要for嵌套循环一遍整个图,找到一个W点就开始一次广搜,把和他连在一起的W变成句号

这样会把一个水坑完全变成旱地,实现了统计个数的操作。

上代码!

//1249
#include<iostream>
#include<queue>
using namespace std;
char dd[115][115];//字符数组,用来读入 
int mapp[115][115];
int go[9][2]{{0,0},{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1}};
struct STU{
    int xx;
    int yy;
};
int m,n,qx,qy,tx,ty,ans;
queue<STU>q;
void bfs(int x,int y)
{
    q.push((STU){x,y});
    mapp[x][y]=0;//把初始的点变成0 
    while(q.size()!=0)
    {
        qx=q.front().xx;
        qy=q.front().yy;
        q.pop();
        for(int i=1;i<=8;i++)
        {
            tx=qx+go[i][0];
            ty=qy+go[i][1];
            if(tx<1||tx>n||ty<1||ty>m)
            {
                continue;
            }
            if(mapp[tx][ty]==0)
            {
                continue;
            }
            mapp[tx][ty]=0;//搜到的1变成0 
            q.push((STU){tx,ty});//全都是模板操作 
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)//这里变成1和0是我个人喜欢,因为我字符串渣一个 
    {
        for(int j=1;j<=m;j++)
        {
            cin>>dd[i][j];
            if(dd[i][j]=='W')
            {
                mapp[i][j]=1;//读入数据,如果输入W,变为1 
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(mapp[i][j]==1)//如果是1,开始搜索 
            {
                ans++;//答案+1 
                bfs(i,j);
            }
        }
    }
    cout<<ans;
    return 0;
}

 这是这道题的写法,看下一类(最短路问题)

ybt1254

 非常经典的最短路问题,可以用广搜解决!

上代码!

//1254
#include<iostream>
#include<queue>
using namespace std;
char dd[110][110];
int mapp[110][110];
int step[110][110];
int go[5][2]{{0,0},{1,0},{-1,0},{0,1},{0,-1}};
struct STU{
    int xx;
    int yy;
};
queue<STU>q;
int n,m,sx,sy,ex,ey,qx,qy,tx,ty;
int bfs(int x,int y)
{
    q.push((STU){x,y});
    mapp[x][y]=1;
    while(q.size()!=0)
    {
        qx=q.front().xx;
        qy=q.front().yy;
        q.pop();
        if(qx==ex&&qy==ey)
        {
            return step[ex][ey];
        }
        for(int i=1;i<=4;i++)
        {
            tx=qx+go[i][0];
            ty=qy+go[i][1];
            if(tx<1||tx>m||ty<1||ty>n)
            {
                continue;
            }
            if(mapp[tx][ty]==1)
            {
                continue;
            }
            mapp[tx][ty]=1;
            step[tx][ty]=step[qx][qy]+1;
            q.push((STU){tx,ty});//以上模板操作,变量名都没换,不需要注释 
        }
    }
}
int main()
{
    cin>>m>>n;
    for(int i=1;i<=m;i++)//这个操作是记录障碍,起点和终点 
    {
        for(int j=1;j<=n;j++)
        {
            cin>>dd[i][j];
            if(dd[i][j]=='S')
            {
                sx=i;
                sy=j;
            }
            if(dd[i][j]=='#')
            {
                mapp[i][j]=1;
            }
            if(dd[i][j]=='T')
            {
                ex=i;
                ey=j;
            }
        }
    }
    cout<<bfs(sx,sy);//输出广搜返回的值 
    return 0;
}

 最短路问题到这就说的差不多了,大家应该也都找到门道了。

只要套模板,再稍微处理一下主程序就好了。

然而,我做题的时候踩了一个大坑,卡了我好长时间,我花了一个小时才搞明白,跟大家分享一下,希望大家不会像我这个蒟蒻一样踩坑叭。

(这里BB一句,还有一个小细节:起数组名的时候,不要和函数库里的函数同名,不然本地跑没事,交到评测网站上会显示CE,比如time,map等等)

ybt1256

这个题也是最短路问题,唯一不同的是他有多组输入数据,所以在进行一次广搜之后需要注意:

清空你的数组和队列!!!

如果没有清空,你的队列里可能会有一些上次搜索的遗物,

数组里可能还记录着上次的路径和步数!!

我就是这么被卡的,然后知道我的队列没清空之后,用了一个错误的方法清队列,导致没清干净,还是WA。

上AC代码(我太难了)

 

//1256
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
char dd[210][210];
int mapp[210][210];
int step[210][210];
int go[5][2]={{0,0},{1,0},{-1,0},{0,1},{0,-1}};
struct STU{
    int xx;
    int yy;
};
queue<STU>q;
int T,m,n,sx,sy,ex,ey,qx,qy,tx,ty,ans,big;
int bfs(int x,int y)
{
    q.push((STU){x,y});
    mapp[x][y]=1;
    while(q.size()!=0)
    {
        qx=q.front().xx;
        qy=q.front().yy;
        q.pop();
        if(qx==ex&&qy==ey)
        {
            return step[ex][ey];
        }
        for(int i=1;i<=4;i++)
        {
            tx=qx+go[i][0];
            ty=qy+go[i][1];
            if(tx<1||tx>m||ty<1||ty>n)
            {
                continue;
            }
            if(mapp[tx][ty]==1)
            {
                continue;
            }
            mapp[tx][ty]=1;
            step[tx][ty]=step[qx][qy]+1;
            q.push((STU){tx,ty});//以上模板操作,不BB 
        }
    }
    return -1;//如果不行,返回-1 
}
int main()
{
    cin>>T;
    for(int i=1;i<=T;i++)
    {
        cin>>m>>n;
        for(int j=1;j<=m;j++)//地图记录到mapp数组中 
        {
            for(int k=1;k<=n;k++)
            {
                cin>>dd[j][k];
                if(dd[j][k]=='S')
                {
                    sx=j;
                    sy=k;
                }
                if(dd[j][k]=='E')
                {
                    ex=j;
                    ey=k;
                }
                if(dd[j][k]=='#')
                {
                    mapp[j][k]=1;
                }
            }
        }
        ans=bfs(sx,sy);//ans记录广搜的返回值,如果返回-1,说明没搜到结果,输出oop! 
        if(ans!=-1)
        {
            cout<<ans<<endl;//返回答案 
        }
        else
        {
            cout<<"oop!"<<endl;
        }
        memset(mapp,0,sizeof(mapp));//清空数组 
        memset(step,0,sizeof(step));
        big=q.size();//就是这里卡的我!这里必须用一个变量记录队列大小,不能放到for的终止条件里 
        for(int l=0;l<big;l++)//如果q.size()放到终止条件里,它会随着你弹出元素改变大小,导致不能清空队列 
        {
            q.pop();//如果没清空队列,里面会有奇怪的东西,干扰结果 
        }
        m=0,n=0,sx=0,sy=0,ex=0,ey=0,qx=0,qy=0,tx=0,ty=0,ans=0;//无视就好了,当时确实是被卡着急了,写了个这个 
    }
    return 0;
}

我再多说一句:

(如果您是while用户,可以写while(q.size!=0)q.pop();)并且我上面说的您可以无视。

我再重申一遍,有多组数据的时候记得清空你的数组和队列!!!

好了,今天的学习分享就到这里,喜欢的别忘了一键三连!!!

 

posted @ 2020-03-23 22:33  ZTer  阅读(472)  评论(3编辑  收藏  举报