`
lgh1992314
  • 浏览: 315611 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

深度优先搜索(DFS)

【算法入门】

郭志伟@SYSUraphealguo(at)qq.com

2012/05/12

1.前言

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

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

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


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

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


2-1寻找V0V6的一条路(无需最短路径)

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

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个运算使其运算结果变成244个数字要不重复的用到计算中。

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

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

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

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

5.2.解法:搜索树

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


5-1

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

6.OJ题目

题目分类来自网络:

sicily10191024103410501052115311711187

pku108811761321141615641753249230833411

7.总结

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

8.扩展

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

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

=========================================================

本文为原创,转载请注明出处:raphealguo@CSDN

作者:raphealguo(at)qq.com

时间:2012/05/12


C语言实现:

#include<iostream>
#include<malloc.h>
using namespace std;

#define maxNum 100 //定义邻接举证的最大定点数
int visited[maxNum];//通过visited数组来标记这个顶点是否被访问过,0表示未被访问,1表示被访问

//图的邻接矩阵表示结构
typedef struct
{
	char v[maxNum];//图的顶点信息
	int e[maxNum][maxNum];//图的顶点信息
	int vNum;//顶点个数
	int eNum;//边的个数
}graph;

void createGraph(graph *g);//创建图g
void DFS(graph *g);//深度优先遍历图g

void dfs(graph *g,int i)
{
	//cout<<"顶点"<<g->v[i]<<"已经被访问"<<endl;
	cout<<"顶点"<<i<<"已经被访问"<<endl;
	visited[i]=1;//标记顶点i被访问
	for(int j=0;j<g->vNum;j++)
	{
		if(g->e[i][j]!=0&&visited[j]==0)
			dfs(g,j);
	}
}

void DFS(graph *g)
{
	int i;
	//初始化visited数组,表示一开始所有顶点都未被访问过
	for(i=0;i<g->vNum;i++)
		visited[i]=0;
	//深度优先搜索
	for(i=0;i<g->vNum;i++)
		if(visited[i]==0)//如果这个顶点为被访问过,则从i顶点出发进行深度优先遍历
			dfs(g,i);
}

void createGraph(graph *g)//创建图g
{
	cout<<"正在创建无向图..."<<endl;
	cout<<"请输入顶点个数vNum:";
	cin>>g->vNum;
	cout<<"请输入边的个数eNum:";
	cin>>g->eNum;
	int i,j;

	//输入顶点信息
	//cout<<"请输入顶点信息:"<<endl;
	//for(i=0;i<g->vNum;i++)
	//	cin>>g->v[i];

	//初始画图g
	for(i=0;i<g->vNum;i++)
		for(j=0;j<g->vNum;j++)
			g->e[i][j]=0;

	//输入边的情况
	cout<<"请输入边的头和尾"<<endl;
	for(int k=0;k<g->eNum;k++)
	{
		cin>>i>>j;
		g->e[i][j]=1;
		g->e[j][i]=1;
	}
}

int main()
{
	graph *g;
	g=(graph*)malloc(sizeof(graph));
	createGraph(g);
	DFS(g);
	int i;
	cin>>i;
	return 0;
}

/*
输入:
正在创建无向图...
请输入顶点个数vNum:10
请输入边的个数eNum:9
请输入边的头和尾
0 1
0 3
1 4
1 5
3 6
4 8
5 2
6 7
8 9
*/


分享到:
评论

相关推荐

    图Graph, 深度优先遍历(DFS), 广度优先遍历(BFS)【数据结构和算法入门9】

    图Graph,_深度优先遍历(DFS),_广度优先遍历(BFS)【数据结构和算法入门9】

    搜索入门——BFS和DFS

    从最基础的迷宫问题看深搜和宽搜的内涵,理解最基础的穷举搜索。

    Java实现深度优先搜索算法(DFS)

    然后,对于起始顶点的每个邻居顶点,如果它尚未被访问过,则调用递归的dfs()方法进行深度优先搜索。 在示例代码中,我们以顶点0作为起始顶点调用dfs(0, visited)方法进行遍历。最终的输出结果将会是按照深度优先...

    搜索入门之BFS深度优先搜索.ppt

    在本主题中,我们主要关注两种搜索算法:BFS(广度优先搜索)和DFS(深度优先搜索),特别是从入门的角度来探讨它们。 首先,搜索算法的核心在于构造解答树,这个树的根节点代表初始状态,而其他节点代表通过应用一...

    c++算法入门学习

    C++中常见的搜索算法有线性搜索、二分搜索、深度优先搜索(DFS)和广度优先搜索(BFS)。二分搜索通常用于有序数据集,其效率远高于线性搜索。而DFS和BFS则是图论和树结构中常用的方法,常用于路径查找、最短路径等...

    算法入门经典第二版.pdf

    搜索算法如二分查找、广度优先搜索(BFS)和深度优先搜索(DFS),这些是解决问题的关键工具。此外,书中还涉及到了动态规划、贪心策略和回溯法等优化算法,这些都是解决复杂问题的利器。 图论部分涵盖了最小生成树...

    搜索bfs,dfs

    总结来说,宽度优先搜索(BFS)和深度优先搜索(DFS)是图论和树结构中不可或缺的搜索策略,它们在ACM竞赛和其他算法挑战中扮演着重要角色。理解并熟练掌握这两种搜索算法,将有助于你解决复杂的问题,并提升编程...

    ACM算法设计-BFS-DFS详解_算法_dfs_ACM_zoouts_bfs_

    在"ACM算法设计-BFS(广度搜索)-DFS入门(深度搜索)详解.ppt"这个文件中,你可能会学到如何使用这两种算法的详细步骤,包括它们的伪代码、实际示例和应用场景。文件可能还会包含相关的编程语言实现,如C++或Python,...

    算法之BFS与DFS

    提供的压缩包文件包含了多个关于BFS和DFS的讲解资料,如"关于bfs与dfs.pdf"、"07_ACM_DFS+BFS.ppt"、"搜索(BFS_和_DFS).ppt"和"ACM算法设计-BFS(广度搜索)-DFS入门(深度搜索)详解.ppt"。这些资源详细介绍了这两种...

    ACM搜索算法,初学算法的入门算法

    这些算法包括宽度优先搜索(BFS)和深度优先搜索(DFS)。BFS确保找到最近的解,而DFS则深入探索所有可能的路径。在图搜索过程中,通常使用栈或队列来存储待处理的节点。 5. **启发式搜索算法**:启发式搜索算法...

    算法竞赛入门经典完整版

    搜索方面,书中会讲解深度优先搜索(DFS)和广度优先搜索(BFS),以及如何运用它们解决实际问题。在图论部分,书中将介绍最小生成树(如Prim算法和Kruskal算法)、最短路径问题(如Dijkstra算法和Floyd-Warshall...

    刘汝佳 算法竞赛入门经典 白皮书ppt

    2. **数据结构**:如链表、栈、队列、树(二叉树、AVL树、红黑树)、图(深度优先搜索DFS、广度优先搜索BFS、最小生成树Prim和Kruskal算法)等,它们是解决问题的关键工具。 3. **动态规划**:讲解如何通过状态转移...

    夜深人静写算法:搜索入门

    除了这两种基本搜索算法之外,还有一些高级搜索算法,例如深度优先搜索(DFS)和广度优先搜索(BFS)。这些算法主要用于图和树结构的搜索,在解决网络爬取、路径规划、游戏开发等问题中非常有用。 深度优先搜索通过...

    《 算法入门经典》代码

    3. **图论算法**:图的遍历(深度优先搜索DFS和广度优先搜索BFS)、最短路径算法(Dijkstra算法、Floyd-Warshall算法)以及最小生成树(Prim算法、Kruskal算法)等,这些都是解决网络流量、社交网络分析等问题的关键...

    算法入门必做题

    2. **搜索算法**:二分查找、深度优先搜索(DFS)、广度优先搜索(BFS)等,它们在解决查找和遍历问题时非常有用。 3. **图论**:包括最短路径问题(Dijkstra算法、Floyd-Warshall算法)、最小生成树(Prim算法、...

    搜索算法-介绍搜索算法入门知识

    搜索算法主要分为两类:宽度优先搜索(Breadth-First Search, BFS)和深度优先搜索(Depth-First Search, DFS)。BFS通常用于找到最短路径或最小步数的解,而DFS则常用于解决回溯问题,如找迷宫的出路。这两种算法都...

    算法 入门

    6. **图算法**:如深度优先搜索(DFS)、广度优先搜索(BFS)、最短路径算法(Dijkstra、Floyd-Warshall、Bellman-Ford)等,这些在网络、路由等问题中至关重要。 7. **贪心算法**:贪心算法通过局部最优解来求全局最优...

    算法竞赛入门经典(小白)标程

    2. **搜索算法**:深度优先搜索(DFS)和广度优先搜索(BFS)是图论问题中常用的策略,对于迷宫求解、树的遍历等问题尤为有效。 3. **图论**:包括最小生成树(Prim或Kruskal)、最短路径(Dijkstra或Floyd-...

    算法入门系列2-在水一方1

    - **图算法** - 包括深度优先搜索(DFS)和广度优先搜索(BFS),用于遍历或搜索图结构。 4. **Python实现** - Python是一种流行的编程语言,因其简洁明了的语法,常用于教学和算法实现。 - 文档中的代码片段...

    算法设计入门教材

    教材会介绍深度优先搜索(DFS)、广度优先搜索(BFS)、最短路径算法(如Dijkstra算法和Floyd-Warshall算法)、最小生成树算法(Prim和Kruskal算法)等。 5. **动态规划**:动态规划是一种强大的解决问题的方法,...

Global site tag (gtag.js) - Google Analytics