[算法入门]——深度优先搜索(DFS)
深度优先搜索(DFS)
深度优先搜索叫DFS(Depth First Search)。OK,那么什么是深度优先搜索呢?_?
样例:
举个例子,你在一个方格网络中,可以简单理解为我们的地图,要从A点到B点找到最短路径:
我们要制定一个策略,以此来建立递归函数。在这种情况下,先往右一直走或往下走,如果往上走或往左走,便必然得不到最优解。
此时你从A点出发,一直朝着右走:
发现右边已经没有可以访问的节点了,再选择朝下递归:
此时找不到可以往右走或往下走的点了,所以只好返回,一直返回到第一个可用节点:
如上重复,在朝下递归:
我们便得到了一个答案:4!虽然程序实际运行情况不会这么简单,所以有时需要考虑更加周到一点。但是我们知道这么多就够了。(你甚至可以写个断点来看它到底干了啥)。
以此,我们可以大体总结一下深度优先搜索的一个基本思想:从一个节点出发,一直到找不到可行节点时,再选择返回。
深度优先搜索是建立在以栈为基础的算法,而递归又恰好符合这个特性,我们可以大致写出深度优先搜索的伪代码:
void dfs(所走的次数, 其他参数)
{
if (找到了符合条件的节点)
{
//更新最小值
return;
}
for (遍历所有方法)
{
//如果找到了一个可行节点,便递归到下一个栈帧
if (该方法可行)
{
dfs(所走次数 + , 其他参数);
}
}
}
但是这样子写会有一个问题:同样的一个节点会被走很多次!这还不是最可怕的,在没有总结出 “如果往上走或往左走,便必然得不到最优解” 的情况下,甚至可能会出现程序放飞自我,A->B,然后B->A,如此死循环然后栈溢出。我们只好引用一个二维数组,只要走过这个节点便对其进行标记表示这里走过了,往后的递归就不能再访问此节点,来避免A->B B->A的尴尬情况。
void dfs(所走的次数, 其他参数)
{
if (找到了符合条件的节点)
{
//更新最小值
return;
}
for (遍历所有方法)
{
if (该方法可行)
{
//标记该节点走过
dfs(所走次数 + , 其他参数);
//回溯:将该节点标记未走过
}
}
}
既然我们写出了代码,为什么还要进行一个叫“回溯(sù)”的事情呢?我们写代码时不能保证一定可以得到最优解,或许从一条路搜索过来需要走5次,从另一条路走过来只要3次。如果没有回溯的话,就会使走过的节点无法再走,更优的解无法覆盖原来的解,需要3次的走法就无法覆盖只需要5次的走法,从而得不到最优解。尽管如此,深度优先搜索的时间开支依旧不小。再打个比方,你已经知道走这条路不是最优解了,但你还是不得不把它走完。所以,我们就需要用到剪枝来剔除不必要的搜索。在这里,剪枝方案就是:如果当前所使用的步数,已经大于等于到终点的所需的步数,那么就舍去这条路线。因为从此处为起点出发的任何一个节点都不可能是最优解了。
int ans = 0x7f7f7f7f //答案,初始值可以看做无限大
void dfs(所走的次数, 其他参数)
{
if (所走的次数 > ans)
{
return;
}
if (找到了符合条件的节点)
{
//更新最小值
return;
}
for (遍历所有方法)
{
if (该方法可行)
{
//标记该节点走过
dfs(所走次数 + , 其他参数);
//回溯:将该节点标记未走过
}
}
}
这样,就是一个标准的深度优先搜索模板。我们也可以针对其他例题进行修改(有些情况甚至连剪枝都剪不了)。
例题(洛谷P2404):
现在引入一道例题:
题目描述
任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。现在给你一个自然数n,要求你求出n的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。
输入格式
输入:待拆分的自然数n。
输出格式
输出:若干数的加法式子。
输入输出样例
条件:
n ≤ 8
//从左到右的参数依次为:不能小于该数,现在选择第几个数字,数字之和
void dfs(int num, int now, int sum)
{
...
}
其中的num可以简化略掉,但是为了方便阅读还是加上去了。
那么,我们还需要一个数组来保存所找到的解,我们把它定义为s[10]。题目中n不会大于8,但是为了避免一些奇奇怪怪的错误,故开为10。很多以索引为1开头的写法,都推荐把数组开大一点(反正评测姬上的内存也不是自家的)。
//s数组用来存储当前的解。题目中给定n不可能大于8,但是为了避免一些奇怪的错误,故将数组s开大一点
int s[], n;
那么,如何判断是否找到了一个可行解呢?其实不难,当参数sum刚好等于n时,即可输出答案(不是大于等于),这样就无需去计算当期解的和,减少运算量。
//找到了一个可行解便打印出来
if (sum == n)
{
for (int i = ;i < now - ;i++)
printf ("%d+", s[i]);
printf ("%d\n", s[now - ]);
return;
}
那么接下来就是枚举所有可行的数字,此时num就派上用场了:接下来可行的数字一定在num到n之间。而now就是存储当前操作第几个数字的索引(索引听不懂的回去补课)
//遍历所有可行数字,如果是i<=n的话,深搜到最后会将n自己打印出来
for (int i = num;i < n;i++)
{
s[now] = i;
dfs(i, now + , sum + i);
s[now] = ; //此行可省略
}
代码中第6行可以省略。为什么可以省略呢?因为以我们的写法,是不会再次访问s[now]的,所以就没必要回溯。
全部代码:
#include <cstdio>
//s数组用来存储当前的解。题目中给定n不可能大于8,但是为了避免一些奇怪的错误,故将数组s开大一点
int s[], n;
//从左到右的参数依次为:不能小于该数,现在选择第几个数字,数字之和
void dfs(int num, int now, int sum)
{
//如果总和超过了n,就直接返回
if (sum > n)
return;
//找到了一个可行解便打印出来
if (sum == n)
{
for (int i = ;i < now - ;i++)
printf ("%d+", s[i]);
printf ("%d\n", s[now - ]);
return;
}
//遍历所有可行数字,如果是i<=n的话,深搜到最后会将n自己打印出来
for (int i = num;i < n;i++)
{
s[now] = i;
dfs(i, now + , sum + i);
s[now] = ; //此行可省略
}
}
int main()
{
scanf ("%d", &n);
dfs(, , );
return ;
}
总结:
深度优先搜索其中函数很好写,但是大体思路有一点难以理解,甚至会出现玄学代码的情况(改一个符号便出现完全例外的情况),所以写代码的时候要尽可能严谨,并且不要乱剪枝。深度优先搜索本质上是一个暴力算法,并且多以递归+回溯的形式出现,如果优化和剪枝不好并且数据刁钻,经常出现TLE的情况。
题外话:
这是我写的第一个博客,可能会出现一些奇奇怪怪的错误,不过可以在评论区留言,我一定会改的(艾玛好紧张QAQ)
[算法入门]——深度优先搜索(DFS)的更多相关文章
- 算法总结—深度优先搜索DFS
深度优先搜索(DFS) 往往利用递归函数实现(隐式地使用栈). 深度优先从最开始的状态出发,遍历所有可以到达的状态.由此可以对所有的状态进行操作,或列举出所有的状态. 1.poj2386 Lake C ...
- 【算法入门】深度优先搜索(DFS)
深度优先搜索(DFS) [算法入门] 1.前言深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法.它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解 ...
- [算法&数据结构]深度优先搜索(Depth First Search)
深度优先 搜索(DFS, Depth First Search) 从一个顶点v出发,首先将v标记为已遍历的顶点,然后选择一个邻接于v的尚未遍历的顶点u,如果u不存在,本次搜素终止.如果u存在,那么从u ...
- 深度优先搜索 DFS 学习笔记
深度优先搜索 学习笔记 引入 深度优先搜索 DFS 是图论中最基础,最重要的算法之一.DFS 是一种盲目搜寻法,也就是在每个点 \(u\) 上,任选一条边 DFS,直到回溯到 \(u\) 时才选择别的 ...
- 深度优先搜索DFS和广度优先搜索BFS简单解析(新手向)
深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每个点仅被访问一次,这个过程就是图的遍历.图的遍历常用的有深度优先搜索和广度优先搜索,这两者对于有向图和无向图 ...
- 利用广度优先搜索(BFS)与深度优先搜索(DFS)实现岛屿个数的问题(java)
需要说明一点,要成功运行本贴代码,需要重新复制我第一篇随笔<简单的循环队列>代码(版本有更新). 进入今天的主题. 今天这篇文章主要探讨广度优先搜索(BFS)结合队列和深度优先搜索(DFS ...
- 深度优先搜索DFS和广度优先搜索BFS简单解析
转自:https://www.cnblogs.com/FZfangzheng/p/8529132.html 深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每 ...
- 算法与数据结构基础 - 深度优先搜索(DFS)
DFS基础 深度优先搜索(Depth First Search)是一种搜索思路,相比广度优先搜索(BFS),DFS对每一个分枝路径深入到不能再深入为止,其应用于树/图的遍历.嵌套关系处理.回溯等,可以 ...
- 图的深度优先搜索(DFS)和广度优先搜索(BFS)算法
深度优先(DFS) 深度优先遍历,从初始访问结点出发,我们知道初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接 ...
随机推荐
- springboot整合swagger。完爆前后端调试
web接口开发时在调试阶段最麻烦的就是参数调试,前端需要咨询后端.后端有时候自己也不是很了解.这时候就会造成调试一次接口就需要看一次代码.Swagger帮我们解决对接的麻烦 springboot接入s ...
- 大型Java进阶专题(九) 设计模式之总结
前言 关于设计模式的文章就到这里了,学习这门多设计模式,你是不是有这样的疑惑,发现很多设计模式很类似,经常会混淆某些设计模式.这章节我们将对设计模式做一个总结,看看各类设计模式有什么区别.需要注意 ...
- .net core 拦截socket
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net ...
- 在Ubuntu 18.04中安装Wine QQ、微信、TIM
近日重新安装了Ubuntu 18.04,因此要重新安装一下Wine QQ.微信之类的,完整安装Wine系列软件一直是一个老大难的问题,网上搜集到的博客也比较零散,因此这里特此写篇博客记录一下 0. 这 ...
- Python基础教程(第3版)PDF高清完整版免费下载|百度云盘
百度云盘:Python基础教程(第3版)PDF高清完整版免费下载 提取码:gkiy 内容简介 本书包括Python程序设计的方方面面:首先从Python的安装开始,随后介绍了Python的基础知识和基 ...
- 前端学习(八):CSS
进击のpython ***** 前端学习--CSS 现在的互联网前端分为三层: HTML:超文本标记语言.从语义的角度描述页面结构 CSS:层叠样式表.从审美的角度负责页面样式 JS:Javascri ...
- 【IDEA】HTML通过servlet3.0注解名提交表单到servlet类找不到页面的问题
IDEA一时爽,摸不着头的BUG火葬场 这个问题困扰我整整一天一夜,先是代码检查路径设置找不出问题,后面换tomcat版不行,抱着侥幸心理换IDEA版本意料之中还是没解决问题. 都快想秃了最后终于完美 ...
- SYN 攻击原理及解决方法
原理SYN foold攻击主要针对tcp通信三次握手期间做的手脚,所以要弄懂这个攻击的原理我们首先必须知道tcp三次握手的详细过程 由上图可知tcp三次握手顾名思义要经过三个步骤,这三个步骤分别是 客 ...
- FPGA内部IP核DDS
项目当中需要正弦信号与余弦信号,首先想到了DDS芯片,例如AD9833.AD9834.由于还需要用FPGA 做一些数据处理,后来干脆直接用FPGA 内部的DDSIP核,同时根据IP核内部的相位累加 ...
- 第3章 探索性数据分析(单因子&对比)与可视化
1.理论铺垫 Dataframe和Series均适用 ~集中趋势:均值mean().中位数median().与分位数quantile(q=0.25).众数mode() ~离中趋势:标准差std().方 ...