zoukankan      html  css  js  c++  java
  • 比较基础的广度优先搜索算法!!!

    公告:在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();)并且我上面说的您可以无视。

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

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

  • 相关阅读:
    经典排序——希尔排序
    经典排序——插入排序
    使用Ajax验证用户名是否存在
    样式小记
    JavaScript学习笔记(二)
    SpringBoot访问静态视图的两种方式
    扩展SpringMVC-Configuration
    ViewResolver -Springboot中的视图解析器
    SpringBoot必知必会-yaml基础语法
    IDE 下载资源整理
  • 原文地址:https://www.cnblogs.com/zaza-zt/p/12548611.html
Copyright © 2011-2022 走看看