广度优先搜索--搜索路径问题

                                                                广搜
(一):理解
宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
BFS并不使用经验法则算法。从算法的观点,所有因为展开节点而得到的子节点都会被加进一个先进先出的队列中。一般的实验里,其邻居节点尚未被检验过的节点会被放置在一个被称为 open 的容器中(例如队列或是链表),而被检验过的节点则被放置在被称为 closed 的容器中
广度优先搜索使用队列(queue)来实现,整个过程也可以看做一个倒立的树形:
1、把根节点放到队列的末尾。
2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
(二):图文讲解

刚刚说的广度优先搜索是连通图的一种遍历策略,那就有必要将图先简单解释一下。


图2-1 连通图示例图

如图2-1所示,这就是我们所说的连通图,这里展示的是一个无向图,连通即每2个点都有至少一条路径相连,例如V0到V4的路径就是V0->V1->V4。

初始全部都是白色(未访问

即将搜索起点V0(灰色)

已搜索V0,即将搜索V1、V2、V3

……终点V6被染灰色,终止

找到最短路径

图3-1 寻找V0到V6的过程


一般我们把顶点用V缩写,把边用E缩写。常常我们有这样一个问题,从一个起点开始要到一个终点,我们要找寻一条最短的路径,从图2-1举例,如果我们要求V0到V6的一条最短路(假设走一个节点按一步来算)【注意:此处你可以选择不看这段文字直接看图3-1】,我们明显看出这条路径就是V0->V2->V6,而不是V0->V3->V5->V6。先想想你自己刚刚是怎么找到这条路径的:首先看跟V0直接连接的节点V1、V2、V3,发现没有V6,进而再看刚刚V1、V2、V3的直接连接节点分别是:{V0、V4}{V0、V1、V6}{V0、V1、V5}(这里画删除线的意思是那些顶点在我们刚刚的搜索过程中已经找过了,我们不需要重新回头再看他们了)。这时候我们从V2的连通节点集中找到了V6,那说明我们找到了这条V0到V6的最短路径:V0->V2->V6,虽然你再进一步搜索V5的连接节点集合后会找到另一条路径V0->V3->V5->V6,但显然他不是最短路径。

你会看到这里有点像辐射形状的搜索方式,从一个节点,向其旁边节点传递病毒,就这样一层一层的传递辐射下去,知道目标节点被辐射中了,此时就已经找到了从起点到终点的路径。

我们采用示例图来说明这个过程,在搜索的过程中,初始所有节点是白色(代表了所有点都还没开始搜索),把起点V0标志成灰色(表示即将辐射V0),下一步搜索的时候,我们把所有的灰色节点访问一次,然后将其变成黑色(表示已经被辐射过了),进而再将他们所能到达的节点标志成灰色(因为那些节点是下一步搜索的目标点了),但是这里有个判断,就像刚刚的例子,当访问到V1节点的时候,它的下一个节点应该是V0和V4,但是V0已经在前面被染成黑色了,所以不会将它染灰色。这样持续下去,直到目标节点V6被染灰色,说明了下一步就到终点了,没必要再搜索(染色)其他节点了,此时可以结束搜索了,整个搜索就结束了。然后根据搜索过程,反过来把最短路径找出来,图3-1中把最终路径上的节点标志成绿色。

整个过程的实例图如图3-1所示。


(三):例题介绍


折磨人的搜索,看到这么多的文字太烦人了!!
整理了两种题型;
(1);简单测试能够达到目的地(只需要判断)
(2):复杂一点的,要求出最短的路径(队列的优先级)

直接跟着题目记下自己的理解:
(1):https://cn.vjudge.net/contest/173215#problem/B
题目:B-搜索
问题概述:给定一个m × n (m行, n列)的迷宫,迷宫中有两个位置,gloria想从迷宫的一个位置走到另外一个位置,当然迷宫中有些地方是空地,gloria可以穿越,有些地方是障碍,她必须绕行,从迷宫的一个位置,只能走到与它相邻的4个位置中,当然在行走过程中,gloria不能走到迷宫外面去。令人头痛的是,gloria是个没什么方向感的人,因此,她在行走过程中,不能转太多弯了,否则她会晕倒的。我们假定给定的两个位置都是空地,初始时,gloria所面向的方向未定,她可以选择4个方向的任何一个出发,而不算成一次转弯。gloria能从一个位置走到另外一个位置吗?   
输入:
第1行为一个整数t (1 ≤ t ≤ 100),表示测试数据的个数,接下来为t组测试数据,每组测试数据中, 
  第1行为两个整数m, n (1 ≤ m, n ≤ 100),分别表示迷宫的行数和列数,接下来m行,每行包括n个字符,其中字符'.'表示该位置为空地,字符'*'表示该位置为障碍,输入数据中只有这两种字符,每组测试数据的最后一行为5个整数k, x 1, y 1, x 2, y 2 (1 ≤ k ≤ 10, 1 ≤ x1, x 2 ≤ n, 1 ≤ y 1, y 2 ≤ m),其中k表示gloria最多能转的弯数,(x 1, y 1), (x 2, y2)表示两个位置,“其中x 1,x 2对应列,y 1, y 2对应行“。
输出:
 每组测试数据对应为一行,若gloria能从一个位置走到另外一个位置,输出“yes”,否则输出“no”。
面对这样的迷宫问题大部分都是用到搜索,(现在我也就会用搜索解决)
  ①:要留意此题目的一个坑,输入时得数,(其中x 1,x 2对应列,y 1, y2对应行。 );
  ②:一些题目直接套用模板也是很好解决的;
AC代码:
#include
#include
#include
#include
#include
#include
using namespace std;
//构建搜索图
char maps[105][105];
//标记坐标
int dir[105][105];
//构建m行n列的图
int m,n;
//转弯的次数,需要设定的起,始坐标
int k,startx,starty,endx,endy;
//方向转换数组
int nexts[4][2]={-1,0,1,0,0,-1,0,1};
//结果判断变量
int flag;
struct node
{
    int x;//横坐标
    int y;//纵坐标
    int k;//
};
//广度优先搜索
void bfs()
{
    //初始化一个结构体变量
    //并且将开始的坐标赋值给它;
    node t;
    t.x=startx;
    t.y=starty;
    t.k=-1;
    //不会再遍历开始的位置了
    dir[startx][starty]=1;
    //初始化一个note类型的队列Q
    queue Q;
    //入队
    Q.push(t);
    //当队列为空的时会停止循环
    while(Q.empty()==0)
    {
        node t1;
        t1=Q.front();
        Q.pop();
        //匹配成功直接结束
        if(t1.x==endx&&t1.y==endy&&t1.k<=k)
            flag=true;
        //转换方向寻找;
        for(int i=0;i<4;i++)
        {
            node t2;
            t2.x=t1.x+nexts[i][0];
            t2.y=t1.y+nexts[i][1];
            //再用到while时,只有加一个方向
            //t2.x=t2.x+nexts[i][0];
            //t2.y=t2.y+nexts[i][1];
            //才会成功运行;
            while(t2.x>=1&&t2.x<=m&&t2.y>=1&&t2.y<=n&&maps[t2.x][t2.y]!='*')
            {
                if(dir[t2.x][t2.y]==0)
                {
                    t2.k=t1.k+1;
                    dir[t2.x][t2.y]=1;
                    Q.push(t2);
                }
                //node t3;
                t2.x=t2.x+nexts[i][0];
                t2.y=t2.y+nexts[i][1];
                //t2=t3;
            }
        }
    }
}
int main()
{
    //输入多少组数据
    int N;
    scanf("%d",&N);
    while(N--)
    {
        //输入图的大小
        scanf("%d%d",&m,&n);
        //输入图
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
            scanf(" %c",&maps[i][j]);
        //初始化dir数组
        memset(dir,0,sizeof(dir));
        //输入限定转弯次数;起始坐标;
        scanf("%d%d%d%d%d",&k,&starty,&startx,&endy,&endx);
        //memset(dir,0,sizeof(dir));
        flag=false;
        bfs();
        if(flag==true)
            printf("yes\n");
        else
            printf("no\n");


    }
   // getchar();
    return 0;
}



(2):HDU - 1242 D - Rescue--拯救天使
 题目描述:
天使被MOLIGPY抓住了!他被Moligpy监禁了。这个监狱被描述为一个N * M(N,M < = 200)矩阵。监狱里有墙、路和警 卫。 安吉尔的朋友们都想拯救安吉尔。他们的任务是:接近天使。我们假设“接近天使”就是到达天使停留的位置。当网格中有一个守卫时,
我们必须杀死他(或她)进入网格。我们假设我们向上,向下,向右,向左移动1个单位时间,杀死一个守卫也需要1个单位时间。
我们强大到足以杀死所有的守卫。

你必须计算最小的时间来接近天使。(当然,我们只能移动,向下,向左,向右移动,在边界内移动。)

输入:

第一行包含N和M的两个整数。
然后是N行,每一行都有M个字符。”.“站在路上,”a代表天使,“r”代表天使的每一个朋友。
进程到文件的末尾。
输出:
对于每个测试用例,您的程序应该输出一个整数,以满足所需的最小时间。

如果这样的数字不存在,你应该输出一条含有“可怜的安琪尔一辈子都呆在监狱里”的线路。


我用了我认为为另一个DFS()模版

#include
#include
#include
#include
using namespace std;

char maps[220][220];
int dir[220][220];
int N,M;
int startx,starty,endx,endy;
int mins;
struct node
{
    int x;
    int y;
    int k;
    //优先队列的优先级问题;
    bool operator < (const node &a) const
    {
        return a.k     }
};
int nexts[4][2]={{-1,0},{1,0},{0,-1},{0,1} };


int bfs()
{
    node cur,next ;
    cur.x=startx;
    cur.y=starty;
    cur.k=0;
    dir[startx][starty]=1;
    //优先级不同的定义方式
    priority_queueQ;
    Q.push(cur);
    while(!Q.empty())
    {
        cur=Q.top();
        Q.pop();
        if(cur.x==endx&&cur.y==endy)
            return cur.k;
        for(int i=0;i<4;i++)
        {
            next.x=cur.x+nexts[i][0];
            next.y=cur.y+nexts[i][1];
           if(next.x<0||next.x>=N||next.y<0||next.y>=M||maps[next.x][next.y]=='#')
                continue;
            //while(next.x>=0&&next.x=0&&next.y             //{


                if(!dir[next.x][next.y])
                {
                    dir[next.x][next.y]=1;
                    if(maps[next.x][next.y]=='x')
                        next.k=cur.k+2;
                    else
                        next.k=cur.k+1;


                    Q.push(next);
                }
               // next.x=next.x+nexts[i][0];
                //next.y=next.y+nexts[i][1];
            //}
        }
    }
    return -1;
}


int main()
{
    while(~scanf("%d%d",&N,&M))
    {
        for(int i=0;i             for(int j=0;j             scanf(" %c",&maps[i][j]);
        for(int i=0;i             for(int j=0;j             {
                if(maps[i][j]=='r')
                {
                    startx=i;
                    starty=j;
                }
                if(maps[i][j]=='a')
                {
                    endx=i;
                    endy=j;
                }
            }
        memset(dir,0,sizeof(dir));
        int ans=bfs();
        if(ans!=-1)
            printf("%d\n",ans);
        else
            printf("Poor ANGEL has to stay in the prison all his life.\n");
    }
    return 0;


}

测试数据:

7 8
#.#####.
#.a#..r.
#..#x...
..#..#.#
#...##..
.#......
........

(②):两种方法(对上边题型比较)对比的地方(对于一些变量可以参看上边)

while(!Q.empty())
    {
        t=Q.front();
        Q.pop();
        //匹配成功直接结束
        if(t.x==endx&&t.y==endy&&t.k<=k)
            flag=true;
        //转换方向寻找;
        for(int i=0;i<4;i++)
        {
            t1.x=t.x+nexts[i][0];
            t1.y=t.y+nexts[i][1];
            while(t1.x>=1&&t1.x<=m&&t1.y>=1&&t1.y<=n&&maps[t1.x][t1.y]!='*')
            //if(t1.x<1||t1.x>m||t1.y<1||t1.y>n||maps[t1.x][t1.y]=='*')
              //  continue;
            {
                if(dir[t1.x][t1.y]==0)
                {
                    t1.k=t.k+1;
                    dir[t1.x][t1.y]=1;
                    Q.push(t1);
                }
            //node t3;
            t1.x=t1.x+nexts[i][0];
            t1.y=t1.y+nexts[i][1];
            //t2=t3;
            }
}
    }

其实代码中加注释的地方就体现到了两个的不同,在用到if()语句或者while()语句时就会有不同的处理方式;

这两个里面的判断条件都好理解,就是不让他越界在不碰到障碍的时候执行或者反之结束本次循环。

当用到while()语句时,在while()这个大循环的后面要加上

//node t3;
         t1.x=t1.x+nexts[i][0];
         t1.y=t1.y+nexts[i][1];

         //t2=t3;

而if()循环就不需要,而且两个都可以实现两种题型(判断是否可以到达和到达的最短的路径)。

再看一下另一个代码:

//优先级不同的定义方式
    priority_queueQ;
    Q.push(cur);
    while(!Q.empty())
    {
        cur=Q.top();
        Q.pop();
        if(cur.x==endx&&cur.y==endy)
            return cur.k;
        for(int i=0;i<4;i++)
        {
            next.x=cur.x+nexts[i][0];
            next.y=cur.y+nexts[i][1];
           if(next.x<0||next.x>=N||next.y<0||next.y>=M||maps[next.x][next.y]=='#')
                continue;
            //while(next.x>=0&&next.x=0&&next.y             //{


                if(!dir[next.x][next.y])
                {
                    dir[next.x][next.y]=1;
                    if(maps[next.x][next.y]=='x')
                        next.k=cur.k+2;
                    else
                        next.k=cur.k+1;


                    Q.push(next);
                }
               // next.x=next.x+nexts[i][0];
                //next.y=next.y+nexts[i][1];
            //}
        }
    }

(四):扩展知识点

优先队列是队列的一种,不过它可以按照自定义的一种方式(数据的优先级)来对队列中的数据进行动态的排序

每次的push和pop操作,队列都会动态的调整,以达到我们预期的方式来存储。

例如:我们常用的操作就是对数据排序,优先队列默认的是数据大的优先级高

所以我们无论按照什么顺序push一堆数,最终在队列里总是top出最大的元素。

用法:

示例:将元素5,3,2,4,6依次push到优先队列中,print其输出。

1. 标准库默认使用元素类型的<操作符来确定它们之间的优先级关系。

priority_queue<int> pq;

通过<操作符可知在整数中元素大的优先级高。
故示例1中输出结果为: 6 5 4 3 2

 

2. 数据越小,优先级越高

priority_queue<int, vector<int>, greater<int> >pq; 

其中
第二个参数为容器类型。
第二个参数为比较函数。
故示例2中输出结果为:2 3 4 5 6

3. 自定义优先级,重载比较符号

重载默认的 < 符号

复制代码
struct node
{
    friend bool operator< (node n1, node n2)
    {
        return n1.priority < n2.priority;
    }
    int priority;
    int value;
}; 
复制代码

这时,需要为每个元素自定义一个优先级。

注:重载>号会编译出错,因为标准库默认使用元素类型的<操作符来确定它们之间的优先级关系。
而且自定义类型的<操作符与>操作符并无直接联系


你可能感兴趣的:(广度优先搜索--搜索路径问题)