算法07 深度优先搜索及相关问题详解

深搜与广搜是搜索算法中最常用的两种算法,通过深度优先搜索解决问题还会用到回溯和剪枝,让我们一起进入本章,了解深搜的基本概念和模板,并学会解决一些常见问题。

目录

问题导入

走迷宫问题

如何走?

问题建模

如何表示迷宫地图等信息呢?

如何表示每次移动的过程?

走迷宫问题参考程序

深度优先搜索概述

解决问题时的注意事项

训练:迷宫

参考代码

训练:全排列问题

参考代码


问题导入

走迷宫问题

现在有一个3*3的迷宫,小知在迷宫的左上角,迷宫出口在右下角,请你帮小知算一算,他有多少种方案可以走出迷宫(每个格子不能重复走动)。

迷宫中显示0的点,是不可以走的。小知每次只能到达相邻的上下左右4个格子。

如何走?

1.如果当前出发的格子是终点,则程序结束。

2.每次可以选择的格子有:上(A)、下(B)、左(C)、右(D)。

3.按照顺序尝试每个格子是否可走。

4.找到第一个可以走的格子(假设为B),标记该格子,表示已经走过。

5.再从格子(B)出发,重复上述过程(递归进行)。

6.将格子(B)消除标记,再尝试从格子(C、D)出发去找路线。

问题建模

如何表示迷宫地图等信息呢?

使用一个3*3的二维数组maze[][]来存储迷宫信息,如果值位0表示不可走,1表示可走。

使用一个3*3的二维数组used[][]来标记是否走过,没走过为0,走过的话为1。

例:used[1][2]=1,表示maze[1][2]已经走过。

如何表示每次移动的过程?

每次移动,实际上就是坐标的变化。

上:行标-1,列标不变。

下:行标+1,列标不变。

左:行标不变,列标-1。

右:行标不变,列标+1。

我们可以用一个二维数组表示移动的方向。

例:当前坐标为(1,2),向上移动就是:(1+fx[0][0],2+fx[0][1]),得到(0,2)。

走迷宫问题参考程序

复制代码
int ans=0,maze[6][6],used[6][6],fx[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
void dfs(int x,int y){
    if(x==3&&y==3){ //如果当前找的点就是终点,则方案数+1,结束本次搜索。
        ans++; return ;
    }
    else{
        used[x][y]=1;  //将当前所在的点标记为1,表示走过
        for(int i=0;i<4;i++) { //尝试上、下、左、右4个方向
            int nx=x+fx[i][0];int ny=y+fx[i][1]; //下一次查找的点的坐标nx,ny
            //nx,ny要在地图内,并且可走,并且未被走过。
            if(nx>=1&&nx<=3&&ny>=1&&ny<=3&&used[nx][ny]==0&&maze[nx][ny]==1){
                used[nx][ny]=1;  //表示nx,ny表示走过
                dfs(nx,ny); //从nx,ny再去找
                used[nx][ny]=0; //消除标记,再尝试其他方向。
            }
        }
        used[x][y]=0; //消除标记
    }
}

int main(){
    for(int i=1;i<=3;i++){
        for(int j=1;j<=3;j++){
            cin>>maze[i][j];
        }
    }
    dfs(1,1);
    cout<<ans;
    return 0;
}

深度优先搜索概述

我们走迷宫的过程就是一个深度优先搜索的过程:从可以解决问题的某一个方向出发,并一直深入寻找,找到这个方向可以得到的所有解决方案,如果找不到,则回退到上一步,从另一个方向开始,再次深入寻找。

解决问题时的注意事项

1.首先弄清楚问题的解空间,即迷宫有多大。

2.弄清楚搜索的边界,即到哪一步就该停下,不用再搜。

3.搜索的方向,即可能包含哪几种子问题。

复制代码
void dfs()//参数用来表示状态  
{  
    if(到达终点状态){  
        ...//根据题意添加  
        return;  
    }  
    if(越界或者是不合法状态)  
        return;  
    if(特殊状态)//剪枝
        return ;
    for(所有可能的下一状态){  
        if(状态符合条件){  
            修改操作;//根据题意来添加  
            标记;  
            dfs();  
            (还原标记);  
            //是否还原标记根据题意  
            //如果加上(还原标记)就是 回溯法  
        }  
    }  
}

训练:迷宫

给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

【输入描述】第一行N、M和T,N为行,M为列,T为障碍总数。第二行起点坐标SX,SY,终点坐标FX,FY。接下来T行,每行为障碍点的坐标。

【输出描述】给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方案总数。

【输入样例】2 2 1

1 1 2 2

1 2

【输出样例】1

参考代码

复制代码
#include<bits/stdc++.h>
using namespace std;

int ans=0,maze[6][6],used[6][6];
int Fx[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int n,m,t,sx,sy,fx,fy,tx,ty;

void dfs(int x,int y,int s,int e){
    if(x==s&&y==e){
        ans++; return ;
    }
    else{
        used[x][y]=1;
        for(int i=0;i<4;i++) {
            int nx=x+Fx[i][0];int ny=y+Fx[i][1];
            if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&used[nx][ny]==0&&maze[nx][ny]==0){ 
                used[nx][ny]=1;
                dfs(nx,ny,s,e);
                used[nx][ny]=0;
            }
        }
        used[x][y]=0;
    }
}

int main(){
    cin>>n>>m>>t;
    cin>>sx>>sy>>fx>>fy;
    for(int i=1;i<=t;i++){
        cin>>tx>>ty;
        maze[tx][ty]=1;
    }
    dfs(sx,sy,fx,fy);
    cout<<ans;
    return 0;
}

训练:全排列问题

输入整数n(n<=9),输出1~n的所有排列方式。

例:n=3,全排列为123,132,213,231,312,321。

【输入描述】输入一个正整数n

【输出描述】输出1~n之间的全排列(n<=9),换行输出

【输入样例】3

【输出样例】123

132

213

231

321

312

参考代码

复制代码
int ans[10],used[10];
void dfs(int x,int n){
    if(x>n){
        for(int i=1;i<=n;i++)
            cout<<ans[i];
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++){
        if(!used[i]){
            ans[x]=i;
            used[i]=1;
            dfs(x+1,n);
            used[i]=0;
        }
    }
}
int main(){
    int n;
    cin>>n;
    dfs(1,n);
    return 0;
}

从入门到算法,再到数据结构,查看全部文章请点击此处http://www.bigbigli.com/

相关推荐
董董灿是个攻城狮2 分钟前
AI视觉连载8:传统 CV 之边缘检测
算法
blasit7 小时前
笔记:Qt C++建立子线程做一个socket TCP常连接通信
c++·qt·tcp/ip
AI软著研究员7 小时前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish7 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
颜酱8 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
地平线开发者1 天前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮1 天前
大模型连载2:初步认识 tokenizer 的过程
算法
地平线开发者1 天前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考1 天前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习
HXhlx1 天前
CART决策树基本原理
算法·机器学习