深度优先和广度优先总结和实践2

解救小哈——DFS算法举例
一、问题引入

有一天,小哈一个人去玩迷宫。但是方向感不好的小哈很快就迷路了。小哼得知后便去解救无助的小哈。此时的小哼已经弄清楚了迷宫的地图,现在小哼要以最快的速度去解救小哈。那么,问题来了…
这里写图片描述

二、问题的分析

首先我们用一个二维数组来存储这个迷宫,刚开始的时候,小哼处于迷宫的入口处(1,1),小哈在(p,q)。其实这道题的的本质就在于找从(1,1)到(p,q)的最短路径。

此时摆在小哼面前的路有两条,我们可以先让小哼往右边走,直到走不通的时候再回到这里,再去尝试另外一个方向。

在这里我们规定一个顺序,按照顺时针的方向来尝试(即右→下→左→上)。

我们先来看看小哼一步之内可以到达的点有哪些?只有(1,2)和(2,1)。

根据刚才的策略,我们先往右边走,但右边(1,3)有障碍物,所以只能往下(2,2)这个点走。但是小哈并不在(2,2)这个点上,所以小哼还得继续往下走,直至无路可走或者找到小哈为止。

注意:并不是让我们找到小哈此题就解决了。因为刚才只是尝试了一条路的走法,而这条路并不一定是最短的。刚才很多地方在选择方向的时候都有多种选择,因此我们需要返回到这些地方继续尝试往别的方向走,直到把所有可能都尝试一遍,最后输出最短的一条路径。

例如下图就是一条可行的搜索路径:
这里写图片描述

三、解决问题——深度优先搜索

(1)如何写dfs函数。

dfs函数的功能是解决当前应该怎么办。而小哼处在某个点的时候需要处理的是:先检查小哼是否已经到达小哈的位置,如果没有到达则找出下一步可以走的地方。

为了解决这个问题,此处dfs()函数只需要维护三个参数,分别是当前这个点的x坐标,y坐标以及当前已经走过的步数step。

//dfs函数定义如下:
void dfs(int x,int y,int step)
{
return 0;
}

(2)判断是否已经到达小哈的位置。

只需要判断当前的坐标是否与小哈的坐标相等就可以了,如果相等就标明已经到达小哈的位置。

void dfs(int x,int y,int step)
{
    if(x==p && y==1)  //判断是否到达小哈的位置 
    {
        if(step<min)
            min=step;  //更新最小值
        return; //这步很重要! 
    }
    return 0;
} 

复制代码

(3)如何获得下一个方向的坐标(此处定义一个方向数组)。
复制代码

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

复制代码

通过这个方向数组,使用循环就可以方便地得到下一步的坐标。

这里将下一步的横坐标用tx存储,纵坐标用ty存储。
复制代码

for(k=0;k<=3;k++)
{
    /*计算下一个点的坐标*/
    tx=x+next[k][0];
    ty=y+next[k][1];
}

复制代码

(4)对下一个点(tx,ty)进行判断(是否越界,是否有障碍物,是否已经在路径中)。

在这里我们用book[tx][ty]来记录格子[tx][ty]是否已经在路径中。

如果这个点符合所有的要求,就对这个点进行下一步的扩展,即dfs(tx,ty,step+1)。

注意这里是step+1,因为一旦从这个点开始继续往下尝试,就意味着步数已经增加了1。
复制代码

for(k=0;k<=3;k++)
{
    /*计算下一个点的坐标*/
    tx=x+next[k][0];
    ty=y+next[k][1];
    if(tx<1 || tx>n || ty<1 || ty>m)  //判断是否越界
        continue;
    /*判断该点是否为障碍物或者已经在路径中*/
    if(a[tx][ty]==0 && book[tx][ty]==0)
    {
        book[tx][ty]=1;  //标记这个点已经走过
        dfs(tx,ty,step+1);  //开始尝试下一个点
        book[tx][ty]=0;  //尝试结束,取消这个点的标记 
    } 
}

实现代码如下:
测试数据:

5 4
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
1 1 4 3

广度优先:

#include<iostream>
using namespace std;
int next[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
int n,m;//n表示行 m表示列 
int a[50][50],book[50][50]={0};
struct node{
    int x;//横坐标 
    int y;//纵坐标 
    int f;//父亲在队列中的编号,本题不要输出路径,可以不需要f 
    int s;//步数 
};
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j]; 
        } 
    } 
    int startX,startY,X,Y,tx,ty,flag;
    cin>>startX>>startY>>X>>Y;
    struct node que[2501];//因为地图大小不超过50*50,因此队列扩展不会超过2500个 
    int head=1,tail=1;//初始化队列
    //往队列插入迷宫的入口坐标 
    que[head].x = startX; 
    que[head].y = startY;
    que[head].s = 0;
    que[head].f = 0;
    book[startX][startY] = 1; 
    tail++;
    //当队列不为空的时候循环 
    while(head < tail){
        //枚举4个方向 
        for(int i=0;i<=3;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;
            } 
            //判断是否障碍物或者已经在路径中 
            if(book[tx][ty] == 0 && a[tx][ty] == 0){
                //把这个点标记为已经走过
                //注意广度优先每点只入队一次,所以和深度优先搜索不同,不需要将book数组还原 
                book[tx][ty] = 1;
                //插入新的点的队列 
                que[tail].x = tx;  
                que[tail].y = ty;
                que[tail].f = head;
                //因为这点是head扩展出来的,所以步数是父亲的步数+1 
                que[tail].s = que[head].s+1;
                tail++;
            }
            //如果到目标点了,停止扩展,任务结束,退出循环 
            if(tx == X && ty == Y){
                flag = 1;
                break;
            } 
        } 
        if(flag == 1){
            break;
        } 
        head++;//注意这里千万不要忘记,当一点扩展结束后,head++才能对后面的点进行扩展 
    } 
    //打印队列中末尾的最后一个点的步数
    //注意tail是指向队列队尾(即最后一位)的下一个位置,所以这需要-1 
    cout<<que[tail-1].s<<endl; 
    return 0;
} 

深度优先:

#include<iostream>
using namespace std;
int book[101][101]={0},a[101][101]={0},n,m,X,Y,minn=0;
int next[4][2]={{0,1},{0,-1},{1,0},{-1,0}}; 
int startX,startY,tx,ty;

void dfs(int x,int y,int step){
    if(x == X && y == Y){//判断是否为小哈的位置 
        if(step<minn){//判断脚步数 
            minn = step;
        } 
        return;
    } else{
        for(int i=0;i<=3;i++){//枚举4种走法
            //计算下一个点的坐标 
            tx = x+next[i][0];
            ty = y+next[i][1];
            //判断是否越界 
            if(tx < 1 || tx > n || ty < 1 || ty > m){
                continue;
            } 
            //判断该点是否为障碍物或者已经在路径中 
            if(a[tx][ty] == 0 && book[tx][ty] == 0){
                book[tx][ty] = 1;//标记这个点已经走过 
                dfs(tx,ty,step+1);//开始尝试下一个点 
                book[tx][ty] = 0; //尝试结束,取消这个点的标记 
            } 
        } 
    } 
} 
int main(){
    cin>>n>>m;//n代表行m代表列 
    minn = n*m+1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j]; 
        } 
    } 
    cin>>startX>>startY;
    cin>>X>>Y;
    //从起点开始搜索 
    book[startX][startY] = 1;//标记起点已经在路径中,防止后面重复走
    //第一个参数是起点的x坐标,第二个参数是起点的y坐标,第三个参数是步数为0 
    dfs(startX,startY,0);
    //输出最短路径 
    cout<<minn<<endl; 
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值