【算法入门】

郭志伟@SYSU:raphealguo(at)qq.com

2012/05/12

1.前言

深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法。它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念。

你可以跳过第二节先看第三节,:)

2.深度优先搜索VS广度优先搜索

2.1演示深度优先搜索的过程

还是引用上篇文章的样例图,起点仍然是V0,我们修改一下题目意思,只需要让你找出一条V0到V6的道路,而无需最短路。

图2-1 寻找V0到V6的一条路(无需最短路径)

假设按照以下的顺序来搜索:

1.V0->V1->V4,此时到底尽头,仍然到不了V6,于是原路返回到V1去搜索其他路径;

2.返回到V1后既搜索V2,于是搜索路径是V0->V1->V2->V6,,找到目标节点,返回有解。

这样搜索只是2步就到达了,但是如果用BFS的话就需要多几步。

2.2深度与广度的比较

(你可以跳过这一节先看第三节,重点在第三节)

从上一篇《【算法入门】广度/宽度优先搜索(BFS) 》中知道,我们搜索一个图是按照树的层次来搜索的。

我们假设一个节点衍生出来的相邻节点平均的个数是N个,那么当起点开始搜索的时候,队列有一个节点,当起点拿出来后,把它相邻的节点放进去,那么队列就有N个节点,当下一层的搜索中再加入元素到队列的时候,节点数达到了N2,你可以想想,一旦N是一个比较大的数的时候,这个树的层次又比较深,那这个队列就得需要很大的内存空间了。

于是广度优先搜索的缺点出来了:在树的层次较深&子节点数较多的情况下,消耗内存十分严重。广度优先搜索适用于节点的子节点数量不多,并且树的层次不会太深的情况。

那么深度优先就可以克服这个缺点,因为每次搜的过程,每一层只需维护一个节点。但回过头想想,广度优先能够找到最短路径,那深度优先能否找到呢?深度优先的方法是一条路走到黑,那显然无法知道这条路是不是最短的,所以你还得继续走别的路去判断是否是最短路?

于是深度优先搜索的缺点也出来了:难以寻找最优解,仅仅只能寻找有解。其优点就是内存消耗小,克服了刚刚说的广度优先搜索的缺点。

3.深度优先搜索

3.1.举例

给出如图3-1所示的图,求图中的V0出发,是否存在一条路径长度为4的搜索路径。

图3-1

显然,我们知道是有这样一个解的:V0->V3->V5->V6。

3.2.处理过程

3.3.对应例子的伪代码

这里先给出上边处理过程的对应伪代码。

  1. /**
  2. * DFS核心伪代码
  3. * 前置条件是visit数组全部设置成false
  4. * @param n 当前开始搜索的节点
  5. * @param d 当前到达的深度,也即是路径长度
  6. * @return 是否有解
  7. */
  8. bool DFS(Node n, int d){
  9. if (d == 4){//路径长度为返回true,表示此次搜索有解
  10. return true;
  11. }
  12. for (Node nextNode in n){//遍历跟节点n相邻的节点nextNode,
  13. if (!visit[nextNode]){//未访问过的节点才能继续搜索
  14. //例如搜索到V1了,那么V1要设置成已访问
  15. visit[nextNode] = true;
  16. //接下来要从V1开始继续访问了,路径长度当然要加
  17. if (DFS(nextNode, d+1)){//如果搜索出有解
  18. //例如到了V6,找到解了,你必须一层一层递归的告诉上层已经找到解
  19. return true;
  20. }
  21. //重新设置成未访问,因为它有可能出现在下一次搜索的别的路径中
  22. visit[nextNode] = false;
  23. }
  24. //到这里,发现本次搜索还没找到解,那就要从当前节点的下一个节点开始搜索。
  25. }
  26. return false;//本次搜索无解
  27. }

3.4.DFS函数的调用堆栈

此后堆栈调用返回到V0那一层,因为V1那一层也找不到跟V1的相邻未访问节点

此后堆栈调用返回到V3那一层

此后堆栈调用返回到主函数调用DFS(V0,0)的地方,因为已经找到解,无需再从别的节点去搜别的路径了。

4.核心代码

这里先给出DFS的核心代码。

  1. /**
  2. * DFS核心伪代码
  3. * 前置条件是visit数组全部设置成false
  4. * @param n 当前开始搜索的节点
  5. * @param d 当前到达的深度
  6. * @return 是否有解
  7. */
  8. bool DFS(Node n, int d){
  9. if (isEnd(n, d)){//一旦搜索深度到达一个结束状态,就返回true
  10. return true;
  11. }
  12. for (Node nextNode in n){//遍历n相邻的节点nextNode
  13. if (!visit[nextNode]){//
  14. visit[nextNode] = true;//在下一步搜索中,nextNode不能再次出现
  15. if (DFS(nextNode, d+1)){//如果搜索出有解
  16. //做些其他事情,例如记录结果深度等
  17. return true;
  18. }
  19. //重新设置成false,因为它有可能出现在下一次搜索的别的路径中
  20. visit[nextNode] = false;
  21. }
  22. }
  23. return false;//本次搜索无解
  24. }

当然了,这里的visit数组不一定是必须的,在一会我给出的24点例子中,我们可以看到这点,这里visit的存在只是为了保证记录节点不被重新访问,也可以有其他方式来表达的,这里只给出核心思想。

深度优先搜索的算法需要你对递归有一定的认识,重要的思想就是:抽象!

可以从DFS函数里边看到,DFS里边永远只处理当前状态节点n,而不去关注它的下一个状态。

它通过把DFS方法抽象,整个逻辑就变得十分的清晰,这就是递归之美。

5.另一个例子:24点

5.1.题目描述

想必大家都玩过一个游戏,叫做“24点”:给出4个整数,要求用加减乘除4个运算使其运算结果变成24,4个数字要不重复的用到计算中。

例如给出4个数:1、2、3、4。我可以用以下运算得到结果24:

1*2*3*4 = 24;2*3*4/1 = 24;(1+2+3)*4=24;……

如上,是有很多种组合方式使得他们变成24的,当然也有无法得到结果的4个数,例如:1、1、1、1。

现在我给你这样4个数,你能告诉我它们能够通过一定的运算组合之后变成24吗?这里我给出约束:数字之间的除法中不得出现小数,例如原本我们可以1/4=0.25,但是这里的约束指定了这样操作是不合法的。

5.2.解法:搜索树

这里为了方便叙述,我假设现在只有3个数,只允许加法减法运算。我绘制了如图5-1的搜索树。

图5-1

此处只有3个数并且只有加减法,所以第二层的节点最多就6个,如果是给你4个数并且有加减乘除,那么第二层的节点就会比较多了,当延伸到第三层的时候节点数就比较多了,使用BFS的缺点就暴露了,需要很大的空间去维护那个队列。而你看这个搜索树,其实第一层是3个数,到了第二层就变成2个数了,也就是递归深度其实不会超过3层,所以采用DFS来做会更合理,平均效率要比BFS快(我没写代码验证过,读者自行验证)。

6.OJ题目

题目分类来自网络:

sicily:1019 1024 1034 1050 1052 1153 1171 1187

pku:1088 1176 1321 1416 1564 1753 2492 3083 3411

7.总结

DFS适合此类题目:给定初始状态跟目标状态,要求判断从初始状态到目标状态是否有解。

8.扩展

不知道你注意到没,在深度/广度搜索的过程中,其实相邻节点的加入如果是有一定策略的话,对算法的效率是有很大影响的,你可以做一下简单马周游马周游这两个题,你就有所体会,你会发现你在搜索的过程中,用一定策略去访问相邻节点会提升很大的效率。

这些运用到的贪心的思想,你可以再看看启发式搜索的算法,例如A*算法等。

深度优先搜索(DFS)的更多相关文章

  1. 深度优先搜索DFS和广度优先搜索BFS简单解析(新手向)

    深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每个点仅被访问一次,这个过程就是图的遍历.图的遍历常用的有深度优先搜索和广度优先搜索,这两者对于有向图和无向图 ...

  2. 利用广度优先搜索(BFS)与深度优先搜索(DFS)实现岛屿个数的问题(java)

    需要说明一点,要成功运行本贴代码,需要重新复制我第一篇随笔<简单的循环队列>代码(版本有更新). 进入今天的主题. 今天这篇文章主要探讨广度优先搜索(BFS)结合队列和深度优先搜索(DFS ...

  3. 深度优先搜索DFS和广度优先搜索BFS简单解析

    转自:https://www.cnblogs.com/FZfangzheng/p/8529132.html 深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每 ...

  4. 【算法入门】深度优先搜索(DFS)

    深度优先搜索(DFS) [算法入门] 1.前言深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法.它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解 ...

  5. 深度优先搜索 DFS 学习笔记

    深度优先搜索 学习笔记 引入 深度优先搜索 DFS 是图论中最基础,最重要的算法之一.DFS 是一种盲目搜寻法,也就是在每个点 \(u\) 上,任选一条边 DFS,直到回溯到 \(u\) 时才选择别的 ...

  6. 算法总结—深度优先搜索DFS

    深度优先搜索(DFS) 往往利用递归函数实现(隐式地使用栈). 深度优先从最开始的状态出发,遍历所有可以到达的状态.由此可以对所有的状态进行操作,或列举出所有的状态. 1.poj2386 Lake C ...

  7. HDU(搜索专题) 1000 N皇后问题(深度优先搜索DFS)解题报告

    前几天一直在忙一些事情,所以一直没来得及开始这个搜索专题的训练,今天做了下这个专题的第一题,皇后问题在我没有开始接受Axie的算法低强度训练前,就早有耳闻了,但一直不知道是什么类型的题目,今天一看,原 ...

  8. [LeetCode OJ] Word Search 深度优先搜索DFS

    Given a 2D board and a word, find if the word exists in the grid. The word can be constructed from l ...

  9. 广度优先(bfs)和深度优先搜索(dfs)的应用实例

    广度优先搜索应用举例:计算网络跳数 图结构在解决许多网络相关的问题时直到了重要的作用. 比如,用来确定在互联网中从一个结点到另一个结点(一个网络到其他网络的网关)的最佳路径.一种建模方法是采用无向图, ...

随机推荐

  1. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(23)-权限管理系统-角色组模块

    系列目录 距离上次发布22讲已经有少许日子了,真是太抱歉,最近年关项目比较急,时间太紧,没有时间发布.请大家见谅 接下来我们的目标是 角色组管理 角色组权限设置 用户管理 把角色组授权给用户 给用户分 ...

  2. linq to entity常用操作

    一.聚合函数查询 ; using (xxxEntities db = new xxxEntities()) { sum = db.userinfo.AsNoTracking().Where(d =&g ...

  3. C# 合并及拆分PDF文件

    C# 合并及拆分PDF文件 有时我们可能会遇到下图这样一种情况 — 我们需要的资料或教程被分成了几部分存放在多个PDF文件中,不管是阅读还是保存都不是很方便,这时我们肯定想要把这些PDF文件合并为一个 ...

  4. 2、C#核心编程结构

     本学习主要参考Andrew Troelsen的C#与.NET4高级程序设计,这小节主要述说以下几个东西: Hello World的Main方法: 利用VS2010新建一个控制台应用程序Hello W ...

  5. node实现watcher的困境

    @(node,watcher) watcher,在如今的前端领域已经数见不鲜了.目前流行的gulp流程工具提供了watcher的选项,是我们在开发过程中不需要手动进行触发构建流程,转而根据文件(目录) ...

  6. Uploadify 结合 Web API 2 上传问题

    最近使用jQuery.Uploadify和Web API配合来做上传,碰到问题,还木有办法解决,记录一下: 环境:jQuery 1.10.2,Uploadify 3.2.1,SWFObject 2.2 ...

  7. SQL Server 中怎么查看一个字母的ascii编码或者Unicode编码

    参考文章:微信公众号文章 在sql中怎么查看一个字符的ascii编码,so easy !! select ASCII('a') SELECT CHAR(97) charNum SELECT UNICO ...

  8. wnmp环境搭建

    windows下配置nginx+php环境 刚看到nginx这个词,我很好奇它的读法(engine x),我的直译是“引擎x”,一般引“擎代”表了性能,而“x”大多出现是表示“xtras(额外的效果) ...

  9. Linux网络查看命令

    1.ifconfig 查看当前生效的网卡. 2.ifdown ifup 网卡禁用与启动. 3.netstat -tuln 查看当前tcp/udp通讯端口连接情况. 4.netstat -an 查看当前 ...

  10. Animation

    Animation 效果 用法 1.非常简单,导入两个文件(UIView+SetRect) (UIView+ImageEffects) 源码 github源码:https://github.com/m ...