【DFS(深度优先搜索)详解】看这一篇就够啦

【DFS详解】看这一篇就够啦

  • 🍃1. 算法思想
  • 🍃2. 三种枚举方式
    • 🍃2.1 指数型枚举
    • 🍃2.2 排列型枚举
    • 🍃2.3 组合型枚举
  • 🍃3. 剪枝优化
  • 🍃4. 图的搜索
  • 🍃5. 来几道题试试手
    • 🍃5.1 选数
    • 🍃5.2 火柴棒等式

在这里插入图片描述

🚀欢迎互三👉: 2的n次方_💎💎
🚀所属专栏:数据结构与算法学习⭐⭐
在这里插入图片描述

🍃1. 算法思想

DFS算法的基本思想是从图中的某个顶点v出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,整个进程反复进行直到所有顶点都被访问为止。

在这里插入图片描述
从上图中可以直观的感受到这种思想
就是一条路走到黑的思想,走到无路可走再回退到上一层,再选择另一条路继续一直走,再回退,直到整个遍历完成,深度优先搜索一般是通过递归来实现的,“递”的过程就是往下搜的过程对应着深度,“归”的过程就是回溯,回退上一级

🍃2. 三种枚举方式

🍃2.1 指数型枚举

在这里插入图片描述
指数型枚举是指一共有n个数,每一个数都有两种状态,也就是选或不选,时间复杂度也就是2^n,指数级的
例如3个数的枚举时,每一个数都有选和不选两种状态,可以根据这个画出递归搜索树
在这里插入图片描述

接着用代码实现一下

#include <iostream>
using namespace std;
int n;
int vis[20];
void dfs(int x){
	//表示已经n个数都被判断过了,一种方案已经搜索完成
    if(x > n){
        for(int i = 1;i <= n;i++){
            if(vis[i] == 1)
            cout<<i<<" ";
        }
        cout<<'\n';
        return;
    }
    vis[x] = 2;//表示不选
    dfs(x+1);//继续搜下一个
    vis[x] = 0;//回溯
    vis[x] = 1;//表示选
    dfs(x+1);//继续搜下一个
    vis[x] = 0;//回溯

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

🍃2.2 排列型枚举

排列型枚举是一种生成给定集合所有可能排列的方法,其实在中学阶段我们就学过排列组合的问题,排列是区分顺序的,例如,同样是1 2 3三个数字,1,2, 3和 1,3,2是两种方案。
来看下面的一个例题:
在这里插入图片描述
很简单,就是生成n个数字的全排列方案,在用代码实现的过程中,需要另外再开一个vis数组,表示状态,以此来区分是否被选过

#include <bits/stdc++.h>
using namespace std;
int vis[15];
int a[15];
int n;
void dfs(int x){
	//表示n个数字都已经选过了
    if(x > n){
        for(int i = 1;i <= n;i++){
            cout<<setw(5)<<a[i];//题目中要求5个场宽
        }
        cout<<'\n';
        return;
    }
    for(int i = 1;i <= n;i++){
        if(!vis[i]){
            vis[i] = 1;//选过标记为1
            a[x] = i;//表示该数字被选上了
            dfs(x+1);//继续选下一个数字
            vis[i] = 0;//回溯重置该数字的状态
            a[x] = 0;//,也可以不写,因为数据可以直接覆盖
        }
    }
}

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

也就是依次枚举n个数,当这n个数选出一种方案之后,就回溯,再判断其它分支

🍃2.3 组合型枚举

组合是从n个不同元素中取出m(m≤n)个元素的所有取法,组合不考虑元素的顺序。也就是 1 2 3 和 1 3 2是同一种方案
在这里插入图片描述
这次的dfs中采用了两个参数,一个表示枚举了几个数,一个表示从哪个数开始往后选,因为这次是组合型枚举,例如,在选了1 3 2之前,1 2 3肯定也已经选过了,所以不会有1 3 2这种情况出现,从哪个数开始往后选,都是选的比这个数字典序大的数,不存在字典数大的数排在字典数小的之前的情况,所以要记录从哪个数开始往后选

#include <bits/stdc++.h>
using namespace std;
int a[25];
int n,r;
void dfs(int x,int start){
	//已经选够的情况
    if(x > r){
        for(int i = 1;i<=r;i++){
            cout<<setw(3)<<a[i];
        }
        cout<<'\n';
        return;
    }
    for(int i = start;i<=n;i++){
        a[x] = i;
        dfs(x+1,i+1);//选下一个数字,并且下一个数字的字典序要比本次大,也就是从i+1开始往后选
        a[x] = 0;
    }
}
int main(){
    cin>>n>>r;
    dfs(1,1);
    return 0;
}

🍃3. 剪枝优化

在深度优先搜索(DFS)中,剪枝是一种常用的优化技术,用于减少不必要的搜索空间,从而提高搜索效率。剪枝的核心思想是在搜索过程中,尽早地识别和排除那些不可能产生解的路径或状态,从而避免在这些无效路径上浪费时间和资源。
dfs(深度优先搜索)其实是一种特别暴力的算法,也就是我们常说的暴力搜索,时间复杂度一般都是指数级或阶乘级的这样,这时,剪枝就显得尤为重要,不然特别容易超时
在这里插入图片描述

来看一道洛谷的典型题:P1088:火星人
在这里插入图片描述

这题是不是就是我们之前讲到的全排列类型的题,意思就是给出一个排列方式,按照字典序求这种方式以后的第几种排列

这次用Java实现一下:

public class Main {
    static int n = 0, r = 0;//n个数字,求第r中排列方式
    static int cnt = 0;//记录次数
    static int[] arr = new int[10010];
    static int[] mars = new int[10010];//火星人的排列
    static boolean[] vis = new boolean[10010];//记录状态
    static boolean falg = false;//记录状态,后面用于剪枝

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        r = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            mars[i] = sc.nextInt();
        }
        dfs(1);
    }

    public static void dfs(int x) {
    	//剪枝,后面的不用再去排列了
        if (falg) {
            return;
        }
        if (x > n) {
            cnt++;
            if (cnt == r + 1) {
            	//表示已经找到了答案
                falg = true;
                for (int i = 1; i <= n; i++) {
                    System.out.print(arr[i] + " ");
                }
                System.out.println();
            }
            return;
        }
        //和之前写的排列模板一样
        for (int i = 1; i <= n; i++) {
        	//表示从火星人给出的排列方案开始往后搜索
            if (cnt == 0) {
                i = mars[x];
            }
            if (!vis[i]) {
                arr[x] = i;
                vis[i] = true;
                dfs(x + 1);
                vis[i] = false;
            }
        }
    }
}

这道题我们就很好的利用了剪枝进行优化,不然按照原来算法的时间复杂度肯定是会超时的,当我们找到目标方案之后,后面的方案就没必要进行搜索了,此时直接退出函数,也就是剪枝
每一题的剪枝方案需要具体题目具体分析。

🍃4. 图的搜索

步骤:
1.选择起始点:从图的某个顶点v开始。
2.标记当前顶点:将当前顶点v标记为已访问,以避免重复访问。
3.遍历邻接点:对于v的每个未访问的邻接点w,递归地执行DFS,从w开始。
4.回溯:当没有更多的邻接点可以遍历时,返回到上一步的顶点。

下面看一道例题:

洛谷1683:入门

图的存储:通过二维数组进行存储
怎么往四个方向进行搜索:定义两个方向数组

在这里插入图片描述

#include <iostream>
using namespace std;
const int N = 25;
char arr[N][N];
bool vis[N][N];
int res;
int x, y;
//方向数组,四个方向进行搜索
int dx[4] = { -1,0,1,0 };
int dy[4] = { 0,1,0,-1 };
void dfs(int m,int n){
    for(int i = 0;i < 4;i++){
        int a = m + dx[i];
        int b = n + dy[i];
        if(vis[a][b]) continue;
        if(arr[a][b] != '.')continue;//只能走"."
        if(a < 0 || a >= x) continue;//不能越界
        if(b < 0 || b >= y) continue;
        vis[a][b] = true;
        res++;
        dfs(a,b);//本题不需要回溯,直接往下搜
    }
}

int main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> y >> x;
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++) {
			cin >> arr[i][j];
		}
	}
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++) {
			//从起点开始搜索
			if (arr[i][j] == '@') {
				vis[i][j] = true;
				dfs(i, j);
			}
		}
	}
	cout << res + 1;//加上起点
    return 0;
}

🍃5. 来几道题试试手

🍃5.1 选数

做题点这里👉 : 洛谷P1036

在这里插入图片描述

这道题其实还是之前讲过的组合型枚举,例如样例中是4个数里边选3个进行组合,只不过最后多了一个求和和素数判断

还用Java来实现一下:

public class Main {
    static int n = 0,k = 0;
    static int cnt = 0;
    static int[] arr = new int[50];
    static int[] res = new int[20];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        k = sc.nextInt();
        for(int i = 1;i <= n;i++){
            arr[i] = sc.nextInt();
        }
        dfs(1,1);
        System.out.println(cnt);
    }
    public static boolean isPrime(int num){
        for(int i=2;i*i<=num;i++){
            if(num%i==0)
                return false;
        }
        return true;
    }
    public static void dfs(int x, int start){
        if(x == k + 1){
            int sum = 0;
            //求和
            for(int i =1;i <= k;i++){
                sum += res[i];
            }
            //判断素数,方案数+1
            if(isPrime(sum)){
                cnt++;
            }
            return;
        }

        for(int i = start;i <= n;i ++){
            res[x] = arr[i];
            dfs(x + 1,i + 1);
            res[x] = 0;
        }
    }
}

🍃5.2 火柴棒等式

做题点这里👉 : 洛谷1149
在这里插入图片描述

我们来实现一下:

import java.util.Scanner;
public class Main {
    static int[] match = new int[1000];
    static int[] arr = new int[1000];
    static int n = 0, cnt = 0;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        n -= 4;//等号和加号用的火柴棒
        match[0] = 6;
        match[1] = 2;
        match[2] = 5;
        match[3] = 5;
        match[4] = 4;
        match[5] = 5;
        match[6] = 6;
        match[7] = 3;
        match[8] = 7;
        match[9] = 6;
        //计算10以后的数字用到的火柴帮数量
        for (int i = 10; i < 1000; i++) {
            match[i] = match[i % 10] + match[i / 10];
        }
        dfs(1, 0);
        System.out.println(cnt);
    }

    public static void dfs(int x, int sum) {
    	//超过给出的数量,剪枝
        if (sum > n) {
            return;
        }
        if (x > 3) {
            if (sum == n && arr[1] + arr[2] == arr[3]) {
                cnt++;
            }
            return;
        }
        for (int i = 0; i < 1000; i++) {
            arr[x] = i;
            dfs(x + 1, sum + match[i]);
            arr[x] = 0;
        }
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/783362.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通过端口转发实现docker容器运行时端口更改

通过端口转发实现docker容器运行时端口更改 前言启动容器查看容器ip地址端口转发 前言 关于修改docker正在运行中容器端口&#xff0c;网上大部分分为3类: 1. 删除原有容器重新创建;2. 改配置文件;3. 在现有容器上新提交镜像&#xff0c;用新镜像起新的容器。 1和3属于同一种流…

【Linux】进程补充知识

文章目录 前言磁盘与物理内存 数据交互局部性原理页表 前言 磁盘是计算机唯一的一个机械设备&#xff0c;在磁盘文件系统中&#xff0c;我们了解到&#xff0c;磁盘的数据读取写入相比物理内存&#xff0c;CPU等效率低了很多。但是其作为数据的载体&#xff0c;物理内存与其交…

CentOS7下安装Doris

Doris简介 Apache Doris 是一款基于 MPP 架构的高性能、实时的分析型数据库&#xff0c;以高效、简单、统一的特点被人们所熟知&#xff0c;仅需亚秒级响应时间即可返回海量数据下的查询结果&#xff0c;不仅可以支持高并发的点查询场景&#xff0c;也能支持高吞吐的复杂分析场…

新时代【机器学习】与【Pycharm】:【随机数据生成】与智能【股票市场分析】

目录 第一步&#xff1a;准备工作 1.1 安装必要的库 小李的理解&#xff1a; 1.2 导入库 小李的理解&#xff1a; 第二步&#xff1a;生成和准备数据 2.1 生成随机股票数据 小李的理解&#xff1a; 2.2 数据探索与可视化 小李的理解&#xff1a; 2.3 数据处理 小李…

谷粒商城学习笔记-18-快速开发-配置测试微服务基本CRUD功能

文章目录 一&#xff0c;product模块整合mybatis-plus1&#xff0c;引入依赖2&#xff0c;product启动类指定mapper所在包3&#xff0c;在配置文件配置数据库连接信息4&#xff0c;在配置文件中配置mapper.xml映射文件信息 二&#xff0c;单元测试1&#xff0c;编写测试代码&am…

MySQL学习记录 —— 십칠 CentOS7.9环境下的MySQL8.4 安装和配置

文章目录 1、安装和配置2、MySQL 包位置3、主要程序介绍 本篇开始在之前mysql博客的基础上继续延伸&#xff0c;适合有一定基础的mysql使用者阅读 环境 &#xff1a;CentOS 7.9 root 用户&#xff0c;MySQL 8.4 1、安装和配置 看一下当前系统版本 cat /etc/redhat-release应当…

项目收获总结--MyBatis的知识收获

一、概述 最近几天公司项目开发上线完成&#xff0c;做个收获总结吧~ 今天记录MyBatis的收获和提升。 二、获取自动生成的(主)键值 insert 方法总是返回一个 int 值 &#xff0c;这个值代表的是插入的行数。若表的主键id采用自增长策略&#xff0c;自动生成的键值在 insert…

ubuntu软件源的两种格式和环境变量

1. ubuntu的/etc是什么目录&#xff1f; 在Ubuntu操作系统中&#xff0c;/etc/是一个特殊的目录&#xff0c;它包含系统的配置文件。这些配置文件用于设置各种系统和应用程序的参数和选项。 一般来说&#xff0c;用户可以在这个目录下找到各种重要的配置文件&#xff0c;如网络…

Leetcode—93. 复原 IP 地址【中等】

2024每日刷题&#xff08;140&#xff09; Leetcode—93. 复原 IP 地址 实现代码 class Solution { public:vector<string> restoreIpAddresses(string s) {vector<string> ans;vector<string> path;function<void(int)>dfs [&](int start) {if…

robotframework+python接口自动化的点滴记录

在robotframeworkpython框架上写了两三天的接口自动化&#xff0c;做了一些笔记。 1.在断言的时候经常由于数据类型导致较验不通过&#xff0c;值得注意的是&#xff0c;在定义常量或者变量的时候&#xff0c;使用${}代表int类型&#xff0c;例如${2}就代表数字2&#xff0c;另…

E - Tree and Hamilton Path 2

算出所有路径之和2减去树的直径 #include <bits/stdc.h> using namespace std; typedef long long ll; const int N2e610; ll n,ans; ll e[N],h[N],idx,w[N],ne[N],dis[N]; void add(ll a,ll b,ll c){ e[idx]b,ne[idx]h[a],w[idx]c,h[a]idx; } ll c; void dfs(ll u,…

23款奔驰S400升级原厂后排电动座椅调节有哪些功能

奔驰 S400 商务版升级后排电动座椅后&#xff0c;通常会具备以下功能&#xff1a; • 电动调节功能&#xff1a;可以通过按钮或控制面板来调节座椅的前后、上下、倾斜等位置&#xff0c;以获得最佳的舒适度。 • 座椅加热功能&#xff1a;在寒冷的天气中&#xff0c;座椅加热…

云渲染平台那个好?2024云渲染测评

1.渲染100&#xff08;强烈推荐&#xff09; 以高性价比著称&#xff0c;是预算有限的小伙伴首选。 15分钟0.2,60分钟内0.8;注册填邀请码【5858】可领30元礼包和免费渲染券) 提供了多种机器配置选择(可以自行匹配环境)最高256G大内存机器&#xff0c;满足不同用户需求。支持…

自然语言处理领域介绍及其发展历史

自然语言处理领域介绍及其发展历史 1 NLP2 主要任务3 主要的方法1 基于规则的方法&#xff08;1950-1980&#xff09;2 基于统计的方法&#xff08;传统的机器学习的方法&#xff09;3 Connectionist approach&#xff08;Neural networks&#xff09; 1 NLP 自动的理解人类语…

uniapp父页面调用子页面 组件方法记录

文章目录 导文如何点击父页面&#xff0c;触发子页面函数先写一个子页面的基础内容父元素 如何点击父页面&#xff0c;修改子页面的值先写一个子页面的基础内容父元素 导文 如何点击父页面&#xff0c;触发子页面函数&#xff1f; 如何点击父页面&#xff0c;修改子页面的值&am…

jvisualvm工具使用--添加远程监视

jvisualvm简介 jvisualvm该工具位于jdk的bin目录下&#xff0c;是jdk自带的可用于监控线程、内存情况、查看方法的CPU时间和内存中的对 象、已被GC的对象、反向查看分配的堆栈等&#xff0c;即&#xff1a;Java虚拟机监控、故障排查及性能分析工具。 远程监控方法 以windows端…

最小二乘法实践

食堂饭菜价格表如下图所示&#xff0c;采用最小二乘法估算荤菜、素菜、米饭的价格构成&#xff0c;增加一条记录&#xff0c;两荤22元。 提取训练数据&#xff1a; x z 12 y z 14 2x z 22 x y z 18 x 2y z 23 2x y z 26 3x y z 36 代码如下&#xff1a; i…

事件mousePressEvent、paintEvent、closeEvent、keyPressEvent】

事件 mousePressEvent、paintEvent、closeEvent、keyPressEvent 鼠标样式的设置 按WSAD通过keyPressEvent事件移动按钮 通过事件mousePressEvent获取鼠标位置的相对位置&#xff0c;绝对位置 cusor 鼠标样式设置成十字星 .h #ifndef DEFAULTHANDLEREXAMPLE_H #define DEFAUL…

01:单片机开发前的准备工作

单片机开发前的准备工作 1、 开发环境的安装2、创建工程和文件3、编译代码4、下载到单片机 1、 开发环境的安装 第一步&#xff1a;安装KEIL开发软件&#xff0c;按照如下步骤按照软件 第二步&#xff1a;注册KEIL软件 2、创建工程和文件 第一步&#xff1a;先在F盘创建一个文…

取得了PMP证书后有哪些优势?不清楚的快来看!

拿到PMP证书后&#xff0c;个人可以享受到一系列的福利&#xff0c;这些福利主要包括但不限于以下几个方面&#xff1a; 职业发展优势 PMP证书是项目管理领域的全球权威认证&#xff0c;能证明持证者具备系统的项目管理知识和经验。在求职和职业发展过程中&#xff0c;PMP证书…