【算法学习笔记】17:DFS与BFS

2023-11-10

1 DFS

深度优先搜索常用于解决需要给出所有方案的问题,因为它的搜索顺序就是能够得到一个完整的搜索路径(方案)后回退再去搜索其它的方案。

1.1 例题:排列数字

由于要求所有排列的方案,可以每次从 1.. n 1..n 1..n里拿一个数字,然后记录一下这个数拿过了,再递归地搜索下一个数字,当所有数字都取完之后,就得到了一种方案,将这种方案输出,回退之后去搜下一个方案。

“回退之后去搜下一个方案”,其实就是在每层DFS的时候遍历一下所有的允许使用的数字,作为下一个位置的数字。需要注意的是在进入下一层DFS之前要把这个数放进 p a t h path path里去,并记录一下这个数用过了,然后在回退之后还要把 p a t h path path末尾加入的这个数删掉,并记录一下这个数现在是没使用的状态。

基于这个思想,实现的代码如下:

#include <iostream>
#include <vector>

using namespace std;

const int N = 10;

int n;
vector<int> path;
bool st[N];

void dfs() {
    if (path.size() == n) {
        for (int i = 0; i < n; i ++ ) cout << path[i] << ' ';
        cout << endl;
        return ;
    }
    for (int i = 1; i <= n; i ++ ) {
        if (!st[i]) {
            path.push_back(i);
            st[i] = true;
            dfs();
            path.pop_back();
            st[i] = false;
        }
    }
}

int main() {
    cin >> n;
    dfs();
    return 0;
}

这里是用一个vector存了 p a t h path path,然后在装满(装够 n n n个数)的时候把整个方案输出出来。如果用数组的话,可以将DFS进行到了哪一层(数组里放了多少数字)维护一下,可以放在参数里,也可以直接在全局维护。另外,这里是用一个bool类型的st数组维护了每个数字有没有被使用过,由于数的范围很小,还不到32位int,所以可以直接用一个int值来代替st,也就进行了二进制优化。

另外,将这个int值作为参数在DFS中进行值传递,就不会在下一层里对它有修改了,这样写起来也方便,这里的语义是表示一种“状态”。

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int path[N];
int n;

// 当前放置u位置的数字,目前所有数字使用状态是state
void dfs(int u, int state) {
    if (u == n) {
        for (int i = 0; i < n; i ++ ) cout << path[i] << ' ';
        cout << endl;
        return ;
    }
    for (int i = 1; i <= n; i ++ ) {
        if (state & (1 << i)) continue;
        path[u] = i;
        dfs(u + 1, state + (1 << i));
    }
}

int main() {
    cin >> n;
    // 从第0位置的数开始,状态一开始是全0,所有数字没使用
    dfs(0, 0);
    return 0;
}

1.2 例题:n-皇后问题

这个问题里,由于限制了每行、每列、每个对角线上都只能有一个'Q',所以可以把一些条件融入到搜索顺序中去。比如看到第一个条件“每行只能有一个'Q'”,所以可以DFS的每层搜索这一行的'Q'应该放在哪一列,然后再保证这一列没有放置过(用col数组检查),保证这个位置所在的主正反对角线(用dgudg数组检查)没有放置过。

如何知道一个位置是在哪个正反对角线上的?可以发现正反对角线上的元素 ( x , y ) (x, y) (x,y)的特征,分别是 x + y x + y x+y等于一个固定值,以及 x − y x - y xy等于一个固定值,所以可以开 2 ∗ n 2 * n 2n的一维数组dgudg记录一下。

#include <iostream>

using namespace std;

const int N = 12;

char g[N][N];
bool col[N], dg[2 * N], udg[2 * N];
int n;

// 当前搜索u号行
void dfs(int u) {
    if (u == n) {
        for (int i = 0; i < n; i ++ ) {
            for (int j = 0; j < n; j ++ )
                cout << g[i][j];
            cout << endl;
        }
        cout << endl;
        return ;
    }
    for (int j = 0; j < n; j ++ ) {
        if (col[j] || dg[u + j] || udg[u - j + n]) continue;
        col[j] = dg[u + j] = udg[u - j + n] = true;
        g[u][j] = 'Q';
        dfs(u + 1);
        col[j] = dg[u + j] = udg[u - j + n] = false;
        g[u][j] = '.';
    }
}


int main() {
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            g[i][j] = '.';
    dfs(0);
    return 0;
}

2 BFS

广度优先搜索每次扩展当前结点的所有结点,所以需要一个栈来维护要扩展的结点。由于广度优先搜索每次都是把所有能到的下一步搜完,所以能够得到最短 p a t h path path的解,所以一些不带权求最短路径的问题也可以直接用BFS解决。

注意,在扩展结点的某个下一结点(也就是把这个下一结点加入到队列)的时候,要保证这个结点是没被扩展过的,否则队列里就有重复结点了,就会出现重复访问了。

2.1 例题:走迷宫

这个就是一个不带边权的最短路问题,直接BFS搜一下就行了,遇到墙或者已经扩展过的点就不用扩展了,核心在与到扩展的点的最短距离,等于到当前点的最短距离+1,也就是dist[a][b] = dist[x][y] + 1

注意这里用dist中某个点值为-1表示这个点没有访问过,注意边界:到 ( 0 , 0 ) (0, 0) (0,0)位置自己的距离是0。

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

typedef pair<int, int> PII;

const int N = 110;

// 存图
int g[N][N];
// 四个方向
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
// (0, 0)到每个点的距离
int dist[N][N];

int main() {
    memset(dist, -1, sizeof dist);
    dist[0][0] = 0;
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            cin >> g[i][j];
    // BFS
    queue<PII> q;
    q.push({0, 0});
    while (q.size()) {
        auto& t = q.front();
        q.pop();
        int x = t.first, y = t.second;
        for (int i = 0; i < 4; i ++ ) {
            int a = x + dx[i], b = y + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= m || g[a][b] == 1 || dist[a][b] != -1)
                continue;
            dist[a][b] = dist[x][y] + 1;
            q.push({a, b});
        }
    }
    cout << dist[n - 1][m - 1] << endl;
    return 0;
}

2.2 例题:八数码

这个问题就是把整个九宫格的局面当成一个节点,给定一个局面,就能够知道如何转移到其它局面,以及给出了目标局面,要求最短转移是多少步:
在这里插入图片描述

所以要想个方法来记录局面,可以从上到下从左到右记录到一个字符串里(就相当于二维字符数组展品成了一维),由于知道行数为 3 3 3列数为 3 3 3,可以很方便的对两者的下标进行转换。

另外要记录到每个结点的距离,由于这里的结点是局面(字符串),不能像上一个题一样开个数组记录了,所以可以用哈希表来记录。

其它的和上一题没有本质区别,都是求无权最短路的问题,直接BFS。

#include <iostream>
#include <queue>
#include <unordered_map>

using namespace std;

int bfs(string state) {
    queue<string> q;
    // 记录路径长度
    unordered_map<string, int> d;
    // 起始状态放进去
    q.push(state);
    d[state] = 0;
    // 四个方向
    int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
    // 终止状态,搜索的目标状态
    string end = "12345678x";
    // BFS搜索过程
    while (q.size()) {
        string t = q.front();
        q.pop();
        // 如果能到目标状态就返回距离
        if (t == end)
            return d[t];
        // 因为一会要直接在t上交换,所以这里记录一下到t的距离
        // 也可以直接把t这个当前状态复制一份
        int dist = d[t];
        // 找到x的下标,可以变换成(x, y)的二维形式
        int k = t.find('x');
        int x = k / 3, y = k % 3;
        // 四个方向搜
        for (int i = 0; i < 4; i ++) {
            int a = x + dx[i], b = y + dy[i];
            if (a < 0 || a >= 3 || b < 0 || b >= 3)
                continue;
            // 做交换
            swap(t[a * 3 + b], t[k]);
            // 如果交换后的状态没扩展过
            if (!d.count(t)) {
                // 就扩展一下,还要记录它扩展过了(写入距离)
                d[t] = dist + 1;
                q.push(t);
            }
            // 交换回来,以恢复t
            swap(t[a * 3 + b], t[k]);
        }
    }
    // 到这说明无法搜到end状态
    return -1;
}

int main() {
    char s;
    string state;
    // 读入开始局面,以字符串形式存储
    for (int i = 0; i < 9; i ++) {
        cin >> s;
        state += s;
    }
    // 这里把BFS封装起来了,不过没有递归不写到函数里也行
    cout << bfs(state) << endl;
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【算法学习笔记】17:DFS与BFS 的相关文章

随机推荐

  • Unity的C#编程教程_56_Namespace 详解

    文章目录 Namespaces Tour of Namespaces Namespaces 命名空间使得我们可以组织和管理我们的代码库 假设我们设置一个脚本名叫 Weapon using System Collections using S
  • python整段代码注释-Python中注释(多行注释和单行注释)的用法实例

    Python中注释 多行注释和单行注释 的用法实例 发布时间 2020 09 30 23 18 32 来源 脚本之家 阅读 97 前言 学会向程序中添加必要的注释 也是很重要的 注释不仅可以用来解释程序某些部分的作用和功能 用自然语言描述代
  • main.c:9:21: fatal error: sqlite3.h: 没有那个文件或目录

    今天在 Ubuntu 里看别人代码时 头文件里面有个
  • 2023普华永道中国首席数据官调研

    导读 在中国2 500家最大的上市企业中 首席数据官或类似管理岗的渗透率仅为1 3 远低于全球27 的水平 首席数据官的推广任重道远 其中 金融行业和通讯 媒体与科技行业的首席数据官或类似管理岗的数量位居前两位 也与这几个行业的数字化转型发
  • 【100天精通Python】Day48:Python Web开发_WSGI网络服务器网关接口与使用

    目录 1 WSGI接口 1 1 CGI 简介 1 2 WSGI 简介 1 3 定义 WSGI 接口 1 3 1 应用程序 Application 1 3 2 服务器 Server 1 4 WSGI 接口的使用示例 1 5 WSGI接口的优势
  • bp网络拟合函数 matlab_基于RBF神经网络的曲线拟合

    目前 在人工神经网络的实际应用中 绝大部分的神经网络模型是采用误差逆传播 error BackPropagation BP 网络和它的变化形式径向基函数 Radial Basis Function RBF 神经网络 RBF网络是一种高效的前
  • 微信小程序使用image组件显示图片的方法

    本文实例讲述了微信小程序使用image组件显示图片的方法 分享给大家供大家参考 具体如下 1 效果展示 2 关键代码 index wxml 代码如下
  • Lightgbm 直方图优化算法深入理解

    一 概述 在之前的介绍Xgboost的众多博文中 已经介绍过 在树分裂计算分裂特征的增益时 xgboost 采用了预排序的方法来处理节点分裂 这样计算的分裂点比较精确 但是 也造成了很大的时间开销 为了解决这个问题 Lightgbm 选择了
  • ubuntu16.04 使用astra s摄像头

    Astra相机使用方法 官网链接 https orbbec3d com develop Astra相机 GitHub orbbec ros astra camera ROS wrapper for Astra camera 普通相机 Git
  • mac安装lrzsz后运行卡死解决办法

    lrzsz的安装配置具体参见 https segmentfault com a 1190000012166969 上述完成后 若可以正常使用 万事大吉 如出现卡死的情况 可以查看配置文件 usr local bin iterm2 recv
  • openwrt 之通过uci 设置参数

    在openwrt中 默认一种配置文件 默认的路径 etc config 在这里面的所有配置文件如需要修改只需使用uci 这个指令来修改 以下uci 指令参数 root xxxx uci Usage uci
  • ubuntu自带vim配色方案

    系统版本 ubuntu 16 04 LTS 刚开始用vim的时候 大家可能会觉得默认的语法高亮的颜色不合心意 不过对于vim来说 这并不是一个问题 其实vim的配色方案是可以更改的 既可以选择系统自带的配色方案 也可以从网上下载其它配色方案
  • 简单理解Hadoop(Hadoop是什么、如何工作)

    一 Hadoop主要的任务部署分为3个部分 分别是 Client机器 主节点和从节点 主节点主要负责Hadoop两个关键功能模块HDFS Map Reduce的监督 当Job Tracker使用Map Reduce进行监控和调度数据的并行处
  • linux下部署thinkphp5项目

    准备工作 购买一个linux服务器地址 安装好linux常用的ssh工具 我这边喜欢用xshell敲命令 用filezilla传输文件 这些工具只要到官网下载就好 速度很快的 1 安装phpstudy for linux 安装下载phpst
  • java:JSONArray转byte[]字节数组

    package com xxx huali hualitest json import com alibaba fastjson JSONArray import com alibaba fastjson util Base64 publi
  • C语言运行流程

    在上一篇文章visual studio如何运行并调试C语言代码中写了如何运行并调试代码 我们就明确一个事实 即不论是嵌入式系统 亦或是普通PC电脑 对于程序的运行硬件处理器只能识别0 1的二进制码 从类人语言的C代码 需要经过一系列的转换过
  • 各种算法使用场景

    深度优先搜索BFS VS 广度优先搜索 DFS 算法就是回溯算法 BFS 相对 DFS 的最主要的区别是 BFS 找到的路径一定是最短的 但代价就是空间复杂度可能比 DFS 大很多 递归灵魂三问 labuladong 告诉你 遇到任何递归型
  • SQL Server基础Sql语句复习

    基础至极 1 创建表 create table Course Cno char 4 primary key not null 创建主键 非空 Cname char 40 not null Cpno char 4 Ccredit smalli
  • 软件测试报告bug统计,软件测试中如何有效地写Bug报告

    引言 为公众写过软件的人 大概都收到过很拙劣的bug 计算机程序代码中的错误或程序运行时的瑕疵 译者注 报告 例如 在报告中说 不好用 所报告内容毫无意义 在报告中用户没有提供足够的信息 在报告中提供了错误信息 所报告的问题是由于用户的过失
  • 【算法学习笔记】17:DFS与BFS

    1 DFS 深度优先搜索常用于解决需要给出所有方案的问题 因为它的搜索顺序就是能够得到一个完整的搜索路径 方案 后回退再去搜索其它的方案 1 1 例题 排列数字 由于要求所有排列的方案 可以每次从 1 n 1 n 1 n里拿一个数字 然后记