广搜算法讲解

广度优先搜索——层层递进
广搜是最简便的图搜索算法之一,这一算法也是很多重要的图的算法的原型。
Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

思想:一层一层往外扩散

广度优先搜索较之深度优先搜索之不同在于,深度优先搜索旨在不管有多少条岔路,先一条路走到底,不成功就返回上一个路口然后就选择下一条岔路。而广度优先搜索旨在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路。并重复这样的操作。

来个简单的例子模拟BFS的过程:

(首先,我们默认优先选择靠左或靠下的元素进行访问 )
选择A为源节点---->(继续往下一层搜索)A的子节点有两个,所以搜索到了B和C两个节点---->(继续下一层搜索,按照靠下和靠左优先原则)先搜索B的所有子节点D---->(C的子节点还没有搜素,所以再要搜索C的子节点)C的子节点已经访问了,所以到了下一层搜素,也就是开始搜素D的子节点E---->结束。
因此此图的遍历顺序为:A---->B---->C---->D---->E(注意对比DFS)
这个例子如果不好掌握的话,我们再看一个非常常见的例子吧:

就像我们把一个石子扔进池塘产生的波纹一样,用像波纹这样一层层往外扩散来遍历整个池塘的方法就是BFS的样子。
当然,我们程序中无法在同一时刻向外访问一圈,所以我们采取队列的模式来模拟。(可以看下文具体演示)
队列模拟同时向外的访问如下:

首先我们选源节点入队,访问源节点的每一个next邻节点(也就是让这些节点都入队),源节点的所有next节点都访问完之后,源节点出队,开始访问此时队首的每一个next邻节点(也就是让这些节点都入队)。做完当前队首的所有next邻节点入队之后。对首出队,开始访问此时队首的每一个next邻节点。重复上述过程,直到所有的点都被访问完毕(也就是队列里的数据都出队了)。

总结广搜的思路:像石子激起水花一样,一层层的往外扩散式遍历。(深搜是深入式遍历)
我们拿迷宫的例子直接体会BFS:
迷宫有n行m列的单元格组成,每个单元格要么是空地,要么是障碍物,找到一条从起点到终点的最短路径。
输入迷宫地图和始末点坐标。
比如说是这样的图:

菱形为起始点,心形为终点,六角星为障碍物。我们的解决方法便是,从起始点开始,往下广搜,每次往进搜索一步便让步数+1,搜索到心形时记录下步数,搜索完毕。
我们来模拟一下这个过程:
从(1,1)出发,下一步可以走到(1,2),(2,1)点:

下一步我们要访问上一步走到的两个点的所有子节点(当然,有些点是障碍不能访问):

同理,我们可以得到所有的步骤图:

我们搜到了心形,也就是找到了目标物,我们每一步都会计数,到现在,我们广搜了几层,就是我们当前走到心形的步数。我们通过广搜解决了问题。
重点来了!!!代码实现这个算法思路:
仔细感悟刚刚的步骤,我们用一个队列来模拟这个过程。这里我们还是用一个结构体来实现队列。

struct node {
    int x;//行数 
    int y;//列数 
    int s;//步数 
};
int map[55][55];//地图 
struct node que[2510];//定义了一个队列数组 
int head,tail;//队列头尾
int book[55][55]={0};//标记数组,用来标记已经遍历的点,这可不能往回搜索
//队列初始化,即队列置空 
head=1;
tail=1;
//第一步将(1,1)入队,并标记
que[tail].x=1;
que[tail].y=1;
que[tail].s=0;
tail++;
book[1][1]=1; 
 

然后从(1.1)开始走,访问每一个next邻节点(逐一入队):

对(1,1)扩展完毕之后,那么现在(1,1)对我们已经没有用了,我们让(1,1)出队,很简单只需要:
head++;
接下来我们还要拓展刚刚新拓展出的(1,2)和(2,1)。现在head指向了(1,2),那么我们就拓展head:我们发现(1,2)没有可扩展的子节点,那么我们继续head++,扩展现在的head(也就是(2,1)):

我们不断的拓展(step便是head的步数+1,因为它是head拓展出来的节点),拓展完head的步数,便head出队。如此往下进行,直到找到目标为止。
我们为了方便访问拓展head的子节点,我们定义了一个含有四个方向的next数组。

int next[4][2]={{0,1},//向右 
            {1,0},//向下 
            {0,-1},//向左 
            {-1,0}};//向上 

我们看看完整代码:

#include <stdio.h>

struct node {
    int x;//行数 
    int y;//列数 
    int s;//步数 
};
int map[55][55];//地图 
struct node que[2510];//定义了一个队列数组 
int head,tail;//队列头尾
int book[55][55]={0};//标记数组,用来标记已经遍历的点,这可不能往回搜索
int n,m;//地图大小
int p,q;//目标坐标 

 
void BFS(int sx,int sy)//起始的坐标
{
    int i,tx,ty,flag;//下一点坐标,flag表示有无找到目标 
    //定义的方向数组 
    int next[4][2]={{0,1},//向右 
                    {1,0},//向下 
                    {0,-1},//向左 
                    {-1,0}};//向上

    while(head<tail){//队列不为空的时候,循环 
        for(i=0;i<4;i++){//枚举四个方向 
            //计算下一点坐标 
            tx=que[head].x+next[i][0];
            ty=que[head].y+next[i][1];
            //判断是否越界
            if(tx<1||ty<1||tx>n||ty>m){
                continue;//越界便continue下一方向 
            }
            //判断是否是障碍或者已经在路径中了
            if(map[tx][ty]==0&&book[tx][ty]==0){
                //标记这个点,注意,此时不需要取消标记,因为这个点只需走一次,和深搜不同
                book[tx][ty]=1;
                //入队
                que[tail].x=tx;
                que[tail].y=ty;
                que[tail].s=que[head].s+1;
                tail++; 
            }
            if(tx==p&&ty==q){//到目标了,结束 
                flag=1;
                break; 
            } 
        }
        if(flag==1){
            break; 
        } 
        head++;//已经拓展过子节点的点,出队 
    }
    return ;
}
int main()
{
    int i,j; 
    int sx,sy; 
    scanf("%d %d",&n,&m); //读入地图大小
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            scanf("%d",&map[i][j]);
        }
    }
    scanf("%d %d %d %d",&sx,&sy,&p,&q);//起始点和目标 
    //队列初始化,即队列置空 
    head=1;
    tail=1;
    //第一步将起始点入队,并标记
        que[tail].x=sx;
        que[tail].y=sy;
        que[tail].s=0;
        tail++;
        book[sx][sy]=1; 
    BFS(sx,sy);
    //打印最后一个点(目标点)的步数 
    printf("%d",que[tail-1].s); 
    return 0;
}

思考:这个代码中是否存在一些不可见的bug呢?
答案:如果目标点不可被找到呢?------这就需要聪明的你进行探索了!

好了,如果还想进一步学习BFS的话,不如看完关于BFS实践的一篇题解吧:HDU-2612-Find a way(BFS)题解-C语言
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值