BFS 与 DFS

DFS 深度优先搜索

深度优先搜索,简称DFSDepth First Search),顾名思义,就是搜索时的深度优先。

DFS 的本质其实是暴力枚举,只不过 DFS 在枚举时会检测当前枚举的选项是否符合条件,

如果符合,保存此选项,枚举下一个选项;反之更换一个选项,尝试此选项是否合法;但如果此时

发现没有可枚举的选项了,也就是所有的选项有不合法,进行「回溯」操作,更换上一个选项。

这样,枚举时就可以避免枚举许多无效的状态,以此节省时间。

可以举一个这样的例子:DFS 是一个有点智慧的人,他会想想自己干的事情有没有意义,

没有的话就放弃这个事情;

而枚举就是一个老实的人,他会老老实实的把所有活都干完,但却不知道自己干了一堆

没有意义的事。

刚刚所说 DFS 其实是图论中的一个概念。在搜索中常常指回溯算法,它们之间的关系在此不多说明,

只需知道 DFS 是一种搜索的算法即可。

DFS 的常见形式如下:

void dfs(当前枚举到的选项){
    if(所有的状态都枚举完毕){
        输出/保存结果
        return;
    }
    for(枚举当前选项)
        if(这个选项是合法的){
            记录这个选项
            dfs(下一层枚举的状态);
            取消这个选项
        }
}

给定一个 N×M 方格的迷宫,迷宫里有 T 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标 SX,SY 和终点坐标 FX,FY,每个障碍物的 X,Y,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

NM5

这题我们可以枚举移动的方向,记录,再判断路上有没有障碍物,很明显时间允许,可以 AC,但要是 NM100 呢?

这时,我们就可以使用 DFS 了。

我们枚举上下左右四个方向,同时判断移动的方向有没有障碍物和有没有走过,分别用 anote 记录,

再用一个 sum 记录解的数量,用 x,y 记录当前 dfs 枚举到的位置,代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,t,fx,fy,sx,sy,sum;
int a[6][6],note[6][6],next1[5][2] = {{114514,1919810},{0,1},{1,0},{0,-1},{-1,0}}; // 存储地图和走过的位置
void dfs(int x,int y){
    if(x == fx && y == fy){ // 如果到了终点
        sum ++; // 记录解的数量
        return;
    }
    for(int i = 1;i <= 4;i ++){ // 枚举方向
        int next_x = x + next1[i][0],next_y = y + next1[i][1]; // 计算下一步的坐标
        if(!a[next_x][next_y] && !note[next_x][next_y]){ // 如果没有障碍物且也没走过
            note[next_x][next_y] = 1; // 标记为走过
            dfs(next_x,next_y); // 继续走迷宫
            note[next_x][next_y] = 0; // 走完后恢复,方便下一次枚举
        }
    }
}
int main(){
    cin>>n>>m>>t;
    cin>>sx>>sy>>fx>>fy;
    for(int i = 1;i <= t;i ++){
        int x,y;
        cin>>x>>y;
        a[x][y] = 1;
    } // 以上为输入
    a[sx][sy] = 1; // 标记起点为障碍物,防止无限来回
    for(int i = 0;i <= n + 1;i ++)
        for(int j = 0;j <= m + 1;j ++)
            if(i == 0 || i == n + 1 || j == 0 || j == m + 1)a[i][j] = 1; // 标记边缘为障碍物
    dfs(sx,sy); // go,go,go!开始走迷宫
    cout<<sum; // 输出解的数量
    return 0; // 完结撒花
}

完美 ACDFS 太厉害了吧!

可是,如果我们要的不是迷宫的一个解,而是最优解呢?

DFS 根本不能保证找到最优解!这是因为 DFS 只是暴力枚举的优化,

判断无解情况及时放弃罢了,有很大的随机性。

这时,BFS 就该出场了。

欲知后事如何,且看下回分解。

BFS 广度优先搜索

广度优先搜索,简称 BFSBreadth First Search),顾名思义,就是搜索时的广度优先。

BFS 在枚举时,对于每一个合法(符合条件也没枚举过)的选项,它都会分一个出「分身」,让「分身」去探索此枚举子树,

而「分身」们又会分出新的分身;如果分出的分身走进了死胡同,没有可枚举的选项了,那么就不再进行「分身」。

最终,到达终点的「分身」一定是最优解,因为不是最优解的「分身」都被最优解分身抢先一步,路被最优解分身堵死了(被标记为走过)。

BFSDFS 不同的是,DFS 追求探索的深度,只会沿着一条路走,可以用较短的搜索时间找到一个解,而 BFS 追求搜索的广度,会尝试每一条可能的路径,

虽然因为要创造大量「分身」,不仅空间和时间都比 DFS 差,但是 DFS 不能保证求出的一定是最优解,这时 BFS 就起到了作用。

BFS 既然需要进行「分身」,自然就需要用一个容器存储,我们会注意到,如果有一个「分身」枚举完毕,分完子「分身」后,

那它就不再有用,可以直接丢弃。根据这个特性,我们就可以使用「队列」来存储,依次出队各个「分身」,再将它们的子「分身」入队,

直到有「分身」到达终点或所有「分身」都进入死胡同,返回结果。

BFS 的常见形式如下:

int bfs(初始参数){
    queue<初始参数类型> q;
    q.push(初始参数);
    while(q.size()){
        类型 f = q.front(); (获取队首)
        q.pop(); (分身出队)
        for(枚举选项){
            if(选项合法){
                if(到达目标状态)return 结果;
                标记选项(防止重复枚举)
                q.push(当前选项参数(分身);
            }
        }
    }
  	return 无解;
}

一个迷宫由 RC 列格子组成,有的格子里有障碍物,用 # 表示,不能移动至此处;有的格子是空地,用 .,可以移动至此处。

在迷宫中移动有上下左右四种方式,每次只能移动一格。数据保证起点与终点为 .

给定一个迷宫,求从左上角走到右下角最少需要走多少次?

枚举与记录部分思路与 DFS 相同,只不过,这一次我们要额外记录每一个「分身」所走的步数[^1],

并在最优解「分身」到达终点时返回最优解「分身」的步数,代码。

#include<bits/stdc++.h>
using namespace std;
char maps[100][100]; // 地图
int note[100][100],n,m; // 存储走过的位置
int next_x[] = {-1,1,0,0};
int next_y[] = {0,0,-1,1}; // 不写 if 的技巧
struct dot{
    int x,y,step; // 定义一个结构体用于记录分身的位置与步数
};
int bfs(){
    queue<dot> q; // 队列 q 用于存储分身
    dot first;
    first.x = 1,first.y = 1,first.step = 1; // 初始状态
    q.push(first); // 初始状态入队
    while(q.size()){ // 循环直到没有分身在队列中
        dot f = q.front(); // 获取队首分身
        q.pop(); // 将无用的队首分身杀掉(划)出队
        for(int i = 0;i < 4;i ++){ // 枚举
            dot next_dot = f; 
            next_dot.x += next_x[i];
            next_dot.y += next_y[i];
            next_dot.step ++; // 计算本分身的位置与步数
            if(next_dot.x >= 1 && next_dot.x <= n && next_dot.y >= 1 && next_dot.y <= m && !note[next_dot.x][next_dot.y] && maps[next_dot.x][next_dot.y] != '#'){ // 判断是否超出边界、此处是否有障碍与是否走过
                if(next_dot.x == n && next_dot.y == m)
                    return next_dot.step; // 如果到了终点返回
                note[next_dot.x][next_dot.y] = 1; // 标记为走过
                q.push(next_dot); // 入队新分身
            }
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i = 1;i <= n;i ++)
        for(int j = 1;j <= m;j ++)
            cin>>maps[i][j]; // 输入地图
    cout<<bfs(); // 计算最优解并输出
    return 0; // 完结撒花
}
posted @   ManGo_Mouse  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
加载中…

{{tag.name}}

{{tran.text}}{{tran.sub}}
无对应文字
有可能是
{{input}}
尚未录入,我来提交对应文字
点击右上角即可分享
微信分享提示