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

news2024/11/14 19:11:48

【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.coloradmin.cn/o/1906693.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

淘宝扭蛋机小程序搭建全攻略

一、引言 在数字化时代&#xff0c;线上娱乐方式层出不穷&#xff0c;其中扭蛋机小程序以其独特的互动性和趣味性&#xff0c;受到了广大用户的喜爱。淘宝扭蛋机小程序作为其中的佼佼者&#xff0c;不仅为用户提供了丰富的奖品选择&#xff0c;还通过创新的玩法和营销策略&…

【计算机毕业设计】018基于weixin小程序实习记录

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Scrapy crawl spider 停止工作

Scrapy是一个用于爬取网站数据的流行框架&#xff0c;有时爬虫可能会停止工作&#xff0c;这通常是由多种原因引起的。以下是一些常见问题及其解决方法&#xff1a; 1、问题背景 用户在使用 Scrapy 0.16.2 版本进行网络爬取时遇到问题&#xff0c;具体表现为爬虫在运行一段时间…

OV SSL证书—防止钓鱼攻击的最佳证书

据Menlo Security日前发布的《2023年浏览器安全状况报告》&#xff0c;针对浏览器的高度规避自适应威胁&#xff08;HEAT&#xff09;呈现激增的发展趋势。 钓鱼攻击概率激增&#xff1a; 安全研究人员发现&#xff0c;与上半年相比&#xff0c;2023年下半年基于浏览器的网络…

大野耐一是如何为丰田铸就精益生产的?

在制造业的漫长历史中&#xff0c;无数的革新者和企业家为追求更高效、更精益的生产方式而不懈努力。其中&#xff0c;大野耐一的名字无疑是这段历史中最为耀眼的星辰之一。他&#xff0c;以其卓越的才智和坚韧的毅力&#xff0c;为丰田汽车公司铸就了一套享誉全球的精益生产体…

windows上部署python3.11

hello&#xff0c;大家好&#xff0c;我是一名测试开发工程师&#xff0c;至今已在自动化测试领域深耕9个年头&#xff0c;现已将本人实战多年的多终端自动化测试框架【wyTest】开源啦&#xff0c;在接下来的一个月里&#xff0c;我将免费指导大家使用wyTest&#xff0c;请大家…

软件安全性测试的工具有哪些?

软件安全性测试是确保软件系统在设计和实施过程中能够保护系统的机密性、完整性和可用性。为了进行软件安全性测试&#xff0c;有许多工具可供选择&#xff0c;这些工具可以帮助测试人员发现潜在的安全漏洞和弱点&#xff0c;从而提高软件系统的安全性。 以下是一些常用的软件安…

游戏提示找不到steam_api64.dll无法继续执行代码的处理方法

相信很多人在玩游戏时候打开游戏时候&#xff0c;经常会遇到各式各样的小问题&#xff0c;比如steam_api64.dll丢失或许找不到steam_api64.dll无法打开游戏就是其中常见问题之一&#xff0c;那么遇到steam_api64.dll丢失问题要如何解决呢&#xff1f;今天我就给大家详细分析一下…

无人直播怎么玩,一文带你了解AI小姐姐自动换装玩法

最近经常有小伙伴问我 就是像这种&#xff0c;一刷礼物&#xff0c;小姐姐就换装的视频到底该怎么做 今天就来教大家 如何来制作这种直播视频 第一步&#xff1a;搭建OBS 1、设置屏幕分辨率&#xff1a; 背景&#xff1a;因为一般初始状态&#xff0c;屏幕是横屏的&#xf…

【Linux】进程补充知识

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

阿里达摩院——寻光:用AI,实现视频创作一条龙!

7 月 6 日&#xff0c;在2024 世界人工智能大会&#xff08;WAIC 2024&#xff09;上&#xff0c;阿里达摩院推出了一站式 AI 视频创作平台 —— 寻光&#xff0c;今天带大家提前来了解一下这款工具&#xff5e; 1、关于“寻光” 寻光是一个拥有辅助用户创作剧本、分镜图等&am…

NAT:地址转换技术

为什么会引入NAT&#xff1f; NAT&#xff08;网络地址转换&#xff09;的引入主要是为了解决两个问题 IPv4地址短缺&#xff1a;互联网快速发展&#xff0c;可用的公网IP地址越来越少。网络安全&#xff1a;需要一种方法来保护内部网络不被直接暴露在互联网上。 IPv4 &…

网络服务器配置与管理

网络服务器配置与管理是一个涉及多个方面的领域&#xff0c;它涵盖了从物理硬件的设置到操作系统、网络服务和应用的配置&#xff0c;再到日常维护和安全策略的实施。以下是网络服务器配置与管理的一些核心概念和步骤&#xff1a; 硬件配置&#xff1a; 选择合适的服务器硬件&a…

git简介以及git操作软件下载以及安装教程,git基础指令介绍,持续更新中~

什么是Git&#xff1f; 最近在学一些git的基础指令&#xff0c;仔细地了解了一下git&#xff0c;发现了他的强大功能&#xff0c;分享一下&#xff1a; Git是一个强大的工具&#xff0c;它在软件开发中扮演着至关重要的角色。 Git是一个开源的分布式版本控制系统&#xff0c;…

GET与POST请求的区别【随记】

区别 1. 请求参数的传递方式 2. 安全性 3. 数据量 4. 幂等性 5. 用途 在Java中&#xff0c;GET和POST请求是通过HTTP协议与服务器进行通信的两种常用方法&#xff0c;它们之间有一些关键的区别&#xff1a; 1. 请求参数的传递方式 GET请求&#xff1a;将参数直接附加在U…

书生·浦语2.5开源,推理能力再创新标杆

导读 2024 年 7 月 3 日&#xff0c;上海人工智能实验室与商汤科技联合香港中文大学和复旦大学正式发布新一代大语言模型书⽣浦语2.5&#xff08;InternLM2.5&#xff09;。相比上一代模型&#xff0c;InternLM2.5 有三项突出亮点&#xff1a; 推理能力大幅提升&#xff0c;在…

四、嵌入式技术(考点篇)

1 第二版教材2.4嵌入式内容补充 嵌入式系统是以应用为中心、以计算机技术为基础&#xff0c;并将可配置与可裁减的软、硬件集成于一体的专用计算机系统&#xff0c;需要满足应用对功能、可靠性、成本、体积和功耗等方面的严格要求。 一般嵌入式系统由嵌入式处理器、相关支撑硬…

View->不同刻度大小的刻度尺View

XML文件 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:o…

如何在centos7安装Docker

在centOS7中我们可以使用火山引擎镜像源镜像安装Docker,以下是具体的安装步骤。 step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils Step 2: 添加软件源信息 sudo yum-config-manager --add-repo https://mirrors.ivolces.com/docker/linux/centos/docker-ce.r…

改进Transformer模型其实也不难

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 原理简介 数据介绍 结果展示 完整代码 之前…