深度优先搜索(DFS)详解

深度优先搜索(DFS)

算法入门】

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.对应例子的伪代码

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

/**
 * DFS核心伪代码
 * 前置条件是visit数组全部设置成false
 * @param n 当前开始搜索的节点
 * @param d 当前到达的深度,也即是路径长度
 * @return 是否有解
 */
bool DFS(Node n, int d){
    if (d == 4){//路径长度为返回true,表示此次搜索有解
        return true;
    }  

    for (Node nextNode in n){//遍历跟节点n相邻的节点nextNode,
        if (!visit[nextNode]){//未访问过的节点才能继续搜索  

            //例如搜索到V1了,那么V1要设置成已访问
            visit[nextNode] = true;  

            //接下来要从V1开始继续访问了,路径长度当然要加  

            if (DFS(nextNode, d+1)){//如果搜索出有解
                //例如到了V6,找到解了,你必须一层一层递归的告诉上层已经找到解
                return true;
            }  

            //重新设置成未访问,因为它有可能出现在下一次搜索的别的路径中
            visit[nextNode] = false;  

        }
        //到这里,发现本次搜索还没找到解,那就要从当前节点的下一个节点开始搜索。
    }
    return false;//本次搜索无解
}  

3.4.DFS函数的调用堆栈

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

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

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

4.核心代码

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

/**
 * DFS核心伪代码
 * 前置条件是visit数组全部设置成false
 * @param n 当前开始搜索的节点
 * @param d 当前到达的深度
 * @return 是否有解
 */
bool DFS(Node n, int d){
    if (isEnd(n, d)){//一旦搜索深度到达一个结束状态,就返回true
        return true;
    }  

    for (Node nextNode in n){//遍历n相邻的节点nextNode
        if (!visit[nextNode]){//
            visit[nextNode] = true;//在下一步搜索中,nextNode不能再次出现
            if (DFS(nextNode, d+1)){//如果搜索出有解
                //做些其他事情,例如记录结果深度等
                return true;
            }  

            //重新设置成false,因为它有可能出现在下一次搜索的别的路径中
            visit[nextNode] = false;
        }
    }
    return false;//本次搜索无解
}  

当然了,这里的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*算法等。

时间: 2024-10-23 15:46:24

深度优先搜索(DFS)详解的相关文章

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

深度优先搜索(DFS) [算法入门] 1.前言深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法.它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念. 你可以跳过第二节先看第三节,:) 2.深度优先搜索VS广度优先搜索 2.1演示深度优先搜索的过程还是引用上篇文章的样例图,起点仍然是V0,我们修改一下题目意思,只需要让你找出一条V0到V6的道路,而无需最短

[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 letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be us

qt的资源替换搜索QDir详解

QDir对跨平台的目录操作提供了很多的便利,为了更加方便的提供全局资源的查找,QDir提供了搜索路径替换功能,解决了资源搜索不便的问题,也能提高文件查找的效率. QDir通过已知的路径前缀去搜索并定位文件,搜索路径增加是有序的.从第一个设置的搜索路径开始,是不是觉得和cocos2d的路径搜索非常相似呢. 见如下QT的原版例子 QDir::setSearchPaths("icons", QStringList(QDir::homePath() + "/images")

深度优先搜索DFS和广度优先搜索BFS

DFS简介 深度优先搜索,从起点开始按照某个原则一直往深处走,直到找到解,或者走不下去,走不下去则回溯到前一节点选择另一条路径走,直到找到解为止. BFS简介 广度优先搜索,从起点开始先搜索其相邻的节点,由此向外不断扩散,直到找到解为止. 举例解释 从1开始去寻找5 DFS: 原则:优先选择左手边 过程:1-2-3-4-6-4-5 BFS: 队列情况:1 2.5     5.3 5出来则找到 遍历图中所有点 DFS: 原则:优先选择左手边 过程:1-2-3-4-6-4-5 BFS: 队列情况:1

深度优先搜索(dfs)

关于深度优先搜索的总结: 1 dfs 的基本结构:  void dfs(int x){ if( x 超出边界){ return ; }else{ for(遍历){ if(未访问过){ 访问         ; 打上标记    ; dfs(x + 1) ; 去掉标记    ; //极易忘记 } } } return; } 2 用dfs求全排列: 本来好好的,结果sizeof(pointer) 就完蛋了.神秘的内存错误,而且还能正常的跑出一个不正常的结果出来. 想了解sizeof这个小妖精的看这里

python实现基础的深度优先搜索(DFS, depth first search)解决数的全排列问题

数的全排列,是一个很简单的问题,平时我们用笔用纸就能列出答案,但是数列位多的时候,排列的结果就有非常多了,例如有1,2,3,4,5,6,7,8,9这一个数列,有9个数字,则有9!(9的阶乘)这么多种结果.那是非常大的.今天我就来介绍用深度优先搜索来解决这个数的全排列的问题. 深度优先搜索 首先简单介绍一下深度优先搜索,深度优先搜索的关键在于当下该如何做,至于下一步如何做,就与当下做的一样.深度优先搜索的基本模型为: dfs(step): 判断边界:执行相关操作,返回 尝试每一种可能 for( i

linux-find搜索指令详解

Find 命令参数项详解 Linux  下find 命令在目录结构中搜索文件,并执行指定的操作,它具有许多强大的功能,在运行一个非常耗资源的find命令时通常将它放在后台运行,因为遍历一个很大的文件会很耗费时间 一.命令格式: find pathname  -options[-print -exec  -ok] 二.命令功能 用于在文件树中查找文件,并作出相应的处理(可能访问磁盘) 三.命令参数 Pathname:find命令所查找的目录路径,例如.表示当前路径 /表示系统根目录 -print:

深度优先搜索DFS (poj2386,poj1979, poj3009,poj1321,aoj0033,aoj0118)

深度优先搜索(DFS) 往往利用递归函数实现(隐式地使用栈). 深度优先从最开始的状态出发,遍历所有可以到达的状态.由此可以对所有的状态进行操作,或列举出所有的状态. 1.poj2386 Lake Couting 题意:八连通被认为连接在一起,求总共有多少个水洼? Sample Input: 10 12 W........WW. .WWW.....WWW ....WW...WW. .........WW. .........W.. ..W......W.. .W.W.....WW. W.W.W.

深度优先搜索(DFS: Depth First Search)

深度优先搜索是一种树的遍历方式.与此对应的是广度优先搜索. ? 二叉树的优先搜索: ? 如何把一个数学问题转换为树的深度优先搜索问题: 例如:各位数之和为偶数的一个10位二进制数有几个. 我们来分析一下这个问题,首先一共有10位数,然后每一位数都只有两种状态0,1 这可以看做是一个深度为10的一个二叉树,然后用树的深度优先搜索即可解决问题. ? 用C语言实现的代码结构 void DFS(int depth) { ????if(depth==10)????????//递归出口 ????{ ????