DFS初入门

news2024/11/20 3:32:27

目录

一、前言

二、搜索与暴力法

1、概念

2、搜索的基本思路

3、BFS:一群老鼠走迷宫

4、DFS:一只老鼠走迷宫

三、DFS

1、DFS访问示例

2、DFS的常见操作

3、DFS基础:递归和记忆化搜索

4、DFS的代码框架(大量编码后回头体会)

5、DFS:保护现场、恢复现场

6、DFS:搜索和输出所有路径

(1)模拟路径过程

(2)DFS搜索所有路径

(3)路径问题:BFS 和 DFS


一、前言

DFS 的本质就是递归,不同的是在递归的过程中加点料,由于递归的特殊操作模式,人脑很难模拟整个过程,而从我们熟知的排列数字和八皇后可以看出,基本套路是先枚举本层的所有可能,再进行递归,也就是枚举所有本层兄弟的可能,再向下走,而下一层也是这样的过程。关于是否回溯,其实只要是递归就要回溯,所以必定有返回值,有的人认为恢复现场就是回溯,其实是不正确的。关于是否需要恢复现场,如果后面操作涉及到前面的内容,则需要恢复现场,但是如果是树的重心这道题,则不需要,根本原因在于变量的作用范围,用变量记录了子树中节点的个数,每个节点遍历过程也不需要重复,所以不需要恢复现场。递归的逻辑自洽,是能够在某个节点结束递归(一般是在最后一层或者叶子结点),而且不管哪一层的操作过程都是相同的,就可以使用递归,这样理解树的重心会更加容易些。

二、搜索与暴力法

1、概念

搜索:“暴力法” 算法思想的具体实现。

搜索:“通用” 的方法。一个问题,如果比较难,那么先尝试一下搜索,或许能启发出更好的算法。

技巧:竞赛时遇到不会的难题,用搜索提交一下,说不定部分判题数据很弱,得分了!

【暴力法】

暴力法 (Brute force,又译为蛮力法)。

把所有可能性都列举出来,一一验证。简单直接!

利用计算机强大的计算能力和存储能力。

蛮力法也是一种重要的算法设计技术:

(1)理论上,蛮力法可以解决可计算领域的各种问题。

(2)蛮力法经常用来解决一些较小规模的问题。

(3)对于一些重要的问题蛮力法可以产生一些合理的算法,具备一些实用价值,而且不受问题规模的限制。

(4)蛮力法可以作为某类问题时间性能的底限,来衡量同样问题的更高效算法。

【蛮力的基本方法】⭐⭐⭐

蛮力的基本方法——扫描

关键——依次处理所有元素

基本的扫描技术——遍历

(1)集合的遍历

(2)线性表的遍历

(3)树的遍历

(4)图的遍历

2、搜索的基本思路

【BFS】

Breadth-First Search,宽度优先搜索,或称为广度优先搜索

【DFS】

Depth-First Search,深度优先搜索

3、BFS:一群老鼠走迷宫

  • 老鼠无限多;
  • 在每个路口,都派出部分老鼠探索所有没走过的路;
  • 走某条路的老鼠,如果碰壁无法前行,就停下;
  • 如果到达的路口已经有别的老鼠探索过了,也停下;
  • 所有的道路都会走到,而且不会重复。

全面扩散、逐层递进

4、DFS:一只老鼠走迷宫

  • 只有一只老鼠;
  • 在每个路口,都选择先走右边(当然,选择先走左边也可以),能走多远就走多远;
  • 碰壁无法再继续往前走,回退一步,这一次走左边,然后继续往下走;
  • 能走遍所有的路,而且不会重复 (回退不算重复)。

一路到底、逐层回退

三、DFS

1、DFS访问示例

设先访问左节点,后访问右节点

模拟老鼠走迷宫

访问顺序:{E B A D C G F I H}。

2、DFS的常见操作

DFS的代码比BFS更简短。

DFS的主要操作:

        时间戳

                DFS序

                树深度

                子树节点数

                中序输出

                先序输出

                后序输出

3、DFS基础:递归和记忆化搜索

  • 形式上,递归函数是 “自己调用自己”,是一个不断 “重复” 的过程。
  • 递归的思想,是把大问题逐步缩小,直到变成最小的同类问题的过程,而最后的小问题的解是已知的,一般是给定的初始条件。
  • 到达最小问题后,再“回溯”,把小问题的解逐个带回给更大的问题,最终最大问题也得到了解决。
  • 递归有两个过程:递归前进、递归返回(回溯)。
  • 在递归的过程中,由于大问题和小问题的解决方法完全一样,那么大问题的代码和小问题的代码可以写成一样。
  • 一个递归函数,直接调用自己,实现了程序的复用。

例如:斐波那契数列

递推式:f(n)=f(n-1)+f(n-2)

打印第20个数:

fib=[0]*25
fib[1]=1
fib[2]=1
for i in range(3,21):
    fib[i]=fib[i-1]+fib[i-2]
print(fib[20])

改用递归实现:

def fib(n):
    global cnt
    cnt+=1
    if n==1 or n==2:
        return 1
    return fib(n-1)+fib(n-2)    #递归调用自己2次,复杂度O(2^n)

cnt=0
print(fib(20))
print(cnt)          #递归了cnt=13529次

递推和递归两种代码,结果一样,计算量差别巨大

递推代码:一个 for 循环,计算 20 次。

递归代码:计算第 20 个斐波那契数,共计算 cnt=13529 次。

为什么斐波那契的递归代码如此低效?

 【原因】

代码低效的原因:return fib(n-1)+fib(n-2)

递归调用了自己 2 次,倍增计算 fib(n) 时,共执行了 O(2^n) 次递归

不过,很多递归函数只调用自己一次,不会额外增加计算量。

【改进:记忆化】

递归的过程中做了重复工作,例如 fib(3) 计算了 2 次,其实只算 1 次就够了。

为避免递归时重复计算,可以在子问题得到解决时,就保存结果,再次需要这个结果时,直接返回保存的结果就行了,不继续递归下去。这种存储已经解决的子问题结果的技术称为 “记忆化(Memoization)”。

记忆化是递归的常用优化技术。动态规划也常常用递归写代码,记忆化也是动态规划的关键技术。

import sys
sys.setrecursionlimit(30000)    #设置递归深度
def fib(n):
    global cnt
    cnt+=1
    if n==1 or n==2:
        data[n]=1
        return data[n]
    if data[n]!=0:
        return data[n]
    data[n]=fib(n-1)+fib(n-2)
    return data[n]
data=[0]*3005
cnt=0
print(fib(300))
print(cnt)

递归的关键问题:递归深度不能太大。

Python 默认递归深度 1000,如果递归深度太大,提示 "maximum recursion depth exceeded in comparison"。

用 sys.setrecursionlimit() 设置递归深度。

常常有深度大于 1000 的递归题目

4、DFS的代码框架(大量编码后回头体会)

DFS的框架,请在大量编码的基础上,再回头体会这个框架的作用。

5、DFS:保护现场、恢复现场

在 DFS 框架中,最让初学者费解的是第 10 行和第 12 行。

第 10 行的 used[i]=1,称为“保存现场”,或“占有现场”。

第 12 行的 used[i]=0,称为“恢复现场”,或“释放现场”。

6、DFS:搜索和输出所有路径

【题目描述】给出一张图,输出从起点到终点的所有路径。

【输入描述】第一行是整数 n, m,分别是行数和列数。后面 n 行,每行 m 个符号。'@' 是起点, '*' 是终点,'·' 能走,'#' 是墙壁不能走。在每一步,都按 左-上-右-下 的顺序搜索。在样例中,左上角坐标 (0,0),起点坐标(1,1),终点坐标 (0,2)。1<n,m <7。

【输出描述】输出所有的路径。坐标 (i, j) 用 ij 表示,例如坐标 (0,2) 表示为 02。从左到右是 i,从上到下是 j。

【输入样例】

5 3

.#.

#@.

*..

...

#.#

【输出样例】

from 11 to 02

1:11->21->22->12->02

2:11->21->22->12->13->03->02

3:11->21->22->23->13->03->02

4:11->21->22->23->13->12->02

5:11->12->02

6:11->12->22->23->13->03->02

7:11->12->13->03->02

(1)模拟路径过程

【第一条路经】

  • 从起点 (1,1) 出发,查询它的 “左-上-右-下”哪个方向能走,发现 “左上” 不能走,可以走 “右”。
  • 第一步走到右边的 (2,1)。然后从 (2,1) 继续走,它可以向上走到 (2,0),但是后面就走不通了,退回来改走下面的 (2,2)。
  • 逐步深入走到终点,最后得到一条从起点 (1,1) 到终点 (0,2) 的路径 ''11->21->22->12->02" 。
  • 在这次DFS深入过程中,这条路径上曾经路过的点被 “保存现场”,不允许再次经过。
  • 到达终点后,从终点退回,回溯寻找下一个路径。退回后 “恢复现场”。

【第二条路经】

搜到一条路径后,从终点 (0, 2) 退回到 (1, 2) ,继续走到 (1, 3)、(0, 3)、(0,  2)。

【第三条路经】

  • 从终点 (0,2) 一路退回到 (2,2) 后,才找到新路径。
  • 在退回的过程中,原来被 “保存现场的” (0, 3)、(1, 3)、(0, 2),重新被 “恢复现场”,允许被经过。
  • 例如 (1, 3),在第二条路径中曾用过,这次再搜新路径时,在第三条路径中重新经过了它。
  • 如果不 “恢复现场”,这个点就不能在新路径中重新用了。

【第四条路径】

(2)DFS搜索所有路径

  • “保存现场” 的作用,是禁止重复使用。当搜索一条从起点到终点的路径时,这条路径上经过的点,不能重复经过,否则就兜圈子了,所以路径上的点需要“保存现场”,禁止再经过它。没有经过的点,或者碰壁后退回的点,都不能“保存现场”,这些点可能后面会进入这条路径。
  • “恢复现场” 的作用。当重新搜新的路径时,方法是从终点(或碰壁的点)沿着旧路径逐步退回,每退回一个点,就对这个点“恢复现场”,允许新路径重新经过这个点。例如上图的点 (1,3)。

  • 路径有很多条,需要记录每条路径然后打印,这个代码使用了 “输出最短路径的简单方法”:每到一个点,就在这个点上记录从起点到这个点的路径。
  • P[][] 记录路径,p[i][j] 用字符串记录了从起点到点 (i,j) 的完整路径。
  • 第 13 行把新的点 (nx, ny) 加入到这个路径。这种“简单方法”浪费空间,适用于小图。
def dfs(x,y):
    global num
    for i in range(0,4):
        dir=[(-1,0),(0,-1),(1,0),(0,1)]     #左、上、右、下
        nx,ny=x+dir[i][0],y+dir[i][1]       #新坐标
        if nx<0 or nx>=hx or ny<0 or ny>=wy:    #不在地图范围内
            continue
        if mp[nx][ny]=='*':
            num+=1
            print("%d:%s->%d%d"%(num,p[x][y],nx,ny))    #打印路径
            continue            #不退出,继续找下一个路径
        if mp[nx][ny]=='.':
            mp[nx][ny]='#'      #保存现场。这个点在这次更深的dfs中不能再用
            p[nx][ny]=p[x][y]+'->'+str(nx)+str(ny)  #记录路径
            dfs(nx,ny)
            mp[nx][ny]='.'      #恢复现场,回溯之后,这个点可以再次使用
num=0
wy,hx=map(int,input().split())      #wy行,hx列。用num统计路径数量
a=['']*10
for i in range(wy):
    a[i]=list(input())      #读迷宫
mp=[['']*(10) for i in range(10)]       #二维矩阵mp[][]表示迷宫
for x in range(hx):
    for y in range(wy):
        mp[x][y]=a[y][x]
        if mp[x][y]=='@':   #起点
            sx=x
            sy=y
        if mp[x][y]=='*':   #终点
            tx=x
            ty=y
print("from %d%d to %d%d"%(sx,sy,tx,ty))
p=[['']*10 for i in range(10)]      #记录从起点到点path[i][j]的路径
p[sx][sy]=str(sx)+str(sy)
dfs(sx,sy)                          #搜索并输出所有的路径

(3)路径问题:BFS 和 DFS

搜所有的路径,应该用DFS;如果只搜最短路径,应该用BFS。

在一张图上,从起点到终点的所有路径数量是一个天文数字,读者可以用上面的代码试试一个 8×8 的图,看看路径总数是多少。但是搜最短的路径就比较简单,并不需要把所有路径搜出来之后再比较得到最短路,用BFS可以极快地搜到最短路。DFS适合用来处理暴力搜所有情况的题目

以上,DFS初入门

祝好

 

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

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

相关文章

一个真正的鳗,他清楚自己每天都要刷《剑指offer》(第九天)

跟着博主一起刷题 这里使用的是题库&#xff1a; https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 57 - II. 和为s的连续正数序列剑指 Offer 59 - I. 滑动窗口的最大值剑指 Offer 60. n个骰子的点数剑指 Offer 57 - II. 和为s的连续正数序列 剑指 Offer 57 …

文旅元宇宙热潮来袭,天下秀用“科技之钥”解锁三大价值

让未来照进现实&#xff0c;让现实走进虚拟&#xff0c;元宇宙正成为通往下个时代的船票。2018年上映的电影《头号玩家》&#xff0c;让大部分人首次感触到元宇宙里的沉浸式体验——男主角带上VR头盔后&#xff0c;瞬间就能进入另一个极其逼真的虚拟世界。随着VR、AR、区块链、…

系统回顾MyBatis体验这一优秀的持久层框架

文章目录1.MyBatis2.Mapper代理3.MyBatis配置升级4.配置文件CRUD5.多条件查询6.多条件动态查询7.单条件动态条件查询8.添加数据并主键返回9.更新数据10.删除数据11.参数传递12.注解开发1.MyBatis MyBatis基本上取消了所有的JDBC硬编码&#xff0c;对于单独使用这样的ORM框架&a…

1585_AURIX_TC275_SMU的部分内核寄存器

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 继续看SMU的资料&#xff0c;这次看一部分SMU的内核相关寄存器。这一次整理的内容比较少&#xff0c;而且优点断篇&#xff0c;因此按照序号来分没有保持10页的对齐。 调试相关的寄存器不…

详解外网访问内网DDNS作用 及ddns解析软件使用方法

导语&#xff1a;随着互联网的成熟&#xff0c;家庭宽带的提速&#xff0c;大家对外网访问家庭内网电脑&#xff0c;监控&#xff0c;服务器&#xff0c;存储NAS等设备的需求倍增。目前外网访问内网可以用DDNS动态域名解析的方式&#xff0c;以下本文就来介绍一下原理和实现工具…

ELK日志(3)

EFK日志收集 Elasticsearch: 数据库&#xff0c;存储数据 javalogstash: 日志收集&#xff0c;过滤数据 javakibana: 分析&#xff0c;过滤&#xff0c;展示 javafilebeat: 收集日志&#xff0c;传输到ES或logstash go redis&#xff1a;缓冲数据&#xff0c;等待logstash取数据…

高并发多级缓存架构解决方案 OpenResty、canal搭建及使用流程

高并发多级缓存架构解决方案1、缓存的常规使用方式2、请求流程拆分1、搭建tomcat集群2、搭建OpenRestyOpenResty的目录结构nginx的配置文件lua脚本的执行流程http请求反向代理到tomcat服务器3、OpenResty、Redis的单点故障问题4、防止缓存穿透java中通过redisson实现布隆过滤器…

Mac 下配置 go语言环境

Mac 下配置 go语言环境两种方法安装Go通过Homebrew安装&#xff08;不太推荐&#xff09;通过官网安装 &#xff08;推荐&#xff09;方法一安装Homebrew通过Homebrew安装Go方法二 通过官网进行安装配置go环境配置go环境国内镜像Vscode环境配置Helloworld.go两种方法安装Go 通…

LabVIEW中的VI脚本

LabVIEW中的VI脚本用户可使用VI脚本选板上的VI、函数和相关的属性、方法&#xff0c;通过程序创建、编辑和运行VI。通过VI脚本&#xff0c;可减少重复的VI编辑所需的时间&#xff0c;例如&#xff1a;创建若干类似VI对齐和分布控件显示或隐藏控件标签连接程序框图对象注: 必须先…

【13】Docker_DockerFile | 关键字

目录 1、DockerFile的定义 2、DockerFile内容基本知识 3、Docker执行DockerFile的大致流程 4、DockerFile的关键字 5、举例&#xff1a; 1、DockerFile的定义 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 2、Do…

[前端笔记——HTML介绍] 2.开始学习HTML

[前端笔记——HTML介绍] 2.开始学习HTML1什么是HTML&#xff1f;2剖析一个HTML元素3块级元素和内联元素4空元素5属性6为一个元素添加属性7布尔属性8省略包围属性值的引号9单引号或双引号?10剖析HTML文档11实体引用&#xff1a;在 HTML 中包含特殊字符1什么是HTML&#xff1f; …

LeetCode 17. 电话号码的字母组合

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 17. 电话号码的字母组合&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 17.…

文件操作中的IO流——字节流与字符流

一&#xff0c;IO流1.什么是IO流IO流是存取和读取数据的解决方案2.IO流的作用IO流用于读写数据&#xff0c;这些数据包括本地文件和网络上的一些数据&#xff1b;比如读写本地文件的时候需要用到文件读写的IO流&#xff0c;读写网络上的数据时需要通过Socket套接字来调用数据流…

机器学习:公式推导与代码实现-监督学习单模型

线性回归 线性回归(linear regression)是线性模型的一种典型方法。 回归分析不再局限于线性回归这一具体模型和算法,更包含了广泛的由自变量到因变量的机器学习建模思想。 原理推导 线性回归学习的关键问题在于确定参数w和b,使得拟合输出y与真实输出yi尽可能接近 为了求…

PowerDesigner16.5配置安装与使用

PowerDesigner16.5百度云下载链接 链接&#xff1a;https://pan.baidu.com/s/1b9XUqxVZ8gTqk_9grptcAQ?pwd3pl7 提取码&#xff1a;3pl7 一&#xff1a;软件安装 1.下载安装包&#xff08;包含安装文件、汉化包、注册文件&#xff09; 2.下载后文件内容如下 3.进入安装文件中…

5. 统计学基础1:平均值...四分位数、方差、标准差(均方差)、标准误(标准误差、均方根误差)、 标准分

文章目录1. 平均值、中位数、众数、极差、四分位数&#xff08;即下、中、上四份位数&#xff09;2. 方差&#xff08;Var、D(X) 、σ^2^&#xff09;、标准差【也叫均方差】&#xff08;SD 、σ&#xff09;3. 标准误【也叫标准误差、均方根误差】&#xff08;SE&#xff09;4…

高空探测数据处理--对流层顶选取

对流层的概念(维基百科) 对流层(英语:Troposphere)是地球大气层中最靠近地面的一层,也是地球大气层里密度最高的一层。它蕴含了整个大气层约75%的质量,以及几乎所有的水蒸气及气溶胶。 对流层从地球表面开始向高空伸展,直至对流层顶,即平流层的起点为止。对流层的上…

【Labview】每日一题

&#x1f6a9;write in front&#x1f6a9; &#x1f50e;大家好&#xff0c;我是謓泽&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f3c5;2021年度博客之星物联网与嵌入式开发TOP5&#xff5…

The ATIS Spoken Language Systems Pilot Corpus

摘要 语音研究有巨大的进步在过去使用以下的模式: 定义研究问题收集语料针对性的衡量进展解决这个研究问题 自然语言研究&#xff0c;另一方面&#xff0c;取得了典型的进步在没有任何数据语料的情况下&#xff0c;这能够测试研究的假设。 我们描述了ATISATISATIS试点语料库…

从C到C++及类与对象

目录 从C到C 嵌入式领域常用的GUI 语法的升级 引用 默认参数 函数重载 堆内存 概念和思维的升级 类和对象 类的申明 类的成员函数 常成员、常对象&#xff08;C推荐const而不用#define&#xff0c; mutable &#xff09; 静态成员(属于类不属于对象) 友元(破坏…