一、相关定义

深度优先遍历,也有称为深度优先搜索,简称DFS。其实,就像是一棵树的前序遍历。

初始条件:图G所有顶点均未被访问过,任选一点v。

思想:是从一个顶点V1开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念。

遍历过程:它从图中某个结点v出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。

【DFS适合此类题目】

  • 给定初始状态跟目标状态,要求判断从初始状态到目标状态是否有解。

二、算法过程

以如下图的无向图G4为例,进行图的深度优先搜索:

假设从顶点v1出发进行搜索,在访问了顶点v1之后,选择邻接点v2。因为v2未曾访问,则从v2出发进行搜索。依次类推,接着从v、v8 、v5出发进行搜索。在访问了v5之后,由于v5的邻接点都已被访问,则搜索回到v8。由于同样的理由,搜索继续回到v4,v2直至v1,此时由于v1的另一个邻接点未被访问,则搜索又从v1到v3,再继续进行下去由此,得到的顶点访问序列为:

显然,这是一个递归的过程。为了在遍历过程中便于区分顶点是否已被访问,需附设访问标志数组vis[0…n-1], ,其初值为false,一旦某个顶点被访问,则其相应的分量置为true。

三、代码实现

我们用邻接矩阵的方式,则代码如下所示。

/*    图的DFS遍历    */
//邻接矩阵形式实现
//顶点从1开始
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 105; //最大顶点数
typedef int VertexType; //顶点类型
bool vis[maxn]; struct Graph{ //邻接矩阵表示的图结构
VertexType vex[maxn]; //存储顶点
int arc[maxn][maxn]; //邻接矩阵
int vexnum,arcnum; //图的当前顶点数和弧数
}; void createGraph(Graph &g) //构建无向图
{
cout<<"请输入顶点数和边数:";
cin>>g.vexnum>>g.arcnum; //构造顶点向量
cout<<"请依次输入各顶点:\n";
for(int i=1;i<=g.vexnum;i++){
scanf("%d",&g.vex[i]);
} //初始化邻接矩阵
for(int i=1;i<=g.vexnum;i++){
for(int j=1;j<=g.vexnum;j++){
g.arc[i][j] = 0;
}
} //构造邻接矩阵
VertexType u,v; //分别是一条弧的弧尾(起点)和弧头(终点)
printf("每一行输入一条弧依附的顶点(空格分开):\n");
for(int i=1;i<=g.arcnum;i++){
cin>>u>>v;
g.arc[u][v] = g.arc[v][u] = 1;
}
} //邻接矩阵的深度优先递归算法
void DFS(Graph g,int i)
{
vis[i] = true;
printf("%d\t",g.vex[i]); //打印顶点
for(int j=1;j<=g.vexnum;j++){ //遍历每个顶点
if(g.arc[i][j]==1 && !vis[j]){ //如果顶点j是顶点i的未访问的邻接点
DFS(g,j); //深度优先搜索顶点j
}
}
} //邻接矩阵的深度遍历操作
void DFSTraverse(Graph g)
{
for(int i=1;i<=g.vexnum;i++){
vis[i] = false; //初始化所有顶点状态都是未访问过状态
}
for(int i=1;i<=g.vexnum;i++){
if(!vis[i]){
DFS(g,i); //对未访问的顶点调用DFS,若是连通图,只会执行一次
}
}
} int main()
{
Graph g;
createGraph(g);
DFSTraverse(g);
return 0;
}

如果使用的是邻接表存储结构,其DFSTraverse函数的代码几乎是相同的,只是在递归函数中因为将数组换成了链表而有不同,代码如下。

//邻接表的深度递归算法
void DFS(GraphList g, int i)
{
EdgeNode *p;
vis[i] = true;
printf("%d ", g->adjList[i].data); //打印顶点,也可以其他操作
p = g->adjList[i].firstedge;
while(p)
{
if(!vis[p->adjvex])
{
DFS(g, p->adjvex); //对访问的邻接顶点递归调用
}
p = p->next;
}
} //邻接表的深度遍历操作
void DFSTraverse(GraphList g)
{
int i;
for(i = 0; i < g.numVertexes; i++)
{
vis[i] = false;
}
for(i = 0; i < g.numVertexes; i++)
{
if(!vis[i])
{
DFS(g, i);
}
}
}

分析上述算法,在遍历时,对图中每个顶点至多调用一次DFS 函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程。其耗费的时间则取决于所采用的存储结构。

当用二维数组表示邻接矩阵图的存储结构时,查找每个顶点的邻接点所需时间为O(n2) ,其中n为图中顶点数。

而当以邻接表作图的存储结构时,找邻接点所需时间为O(e),其中e 为无向图中边的数或有向图中弧的数。由此,当以邻接表作存储结构时,深度优先搜索遍历图的时间复杂度为O(n+e) 。  

对比两个不同的存储结构的深度优先遍历算法,显然对于点多边少的稀疏图来说,邻接表结构使得算法在时间效率上大大提高。

四、沙场练兵

题目一、滑雪

题目二、棋盘问题

DFS做题小结

五、知识扩展

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

数据结构6——DFS的更多相关文章

  1. 【数据结构】DFS求有向图的强连通分量

    用十字链表结构写的,根据数据结构书上的描述和自己的理解实现.但理解的不透彻,所以不知道有没有错误.但实验了几个都ok. #include <iostream> #include <v ...

  2. 数据结构之DFS与BFS实现

    本文主要包括以下内容 邻接矩阵实现无向图的BFS与DFS 邻接表实现无向图的BFS与DFS 理论介绍 深度优先搜索介绍 图的深度优先搜索(Depth First Search),和树的先序遍历比较类似 ...

  3. 数据结构之DFS与BFS

    深度搜索(DFS) and  广度搜索(BFS) 代码如下: #include "stdafx.h" #include<iostream> #include<st ...

  4. 数据结构 《2》----基于邻接表表示的图的实现 DFS(递归和非递归), BFS

    图通常有两种表示方法: 邻接矩阵 和 邻接表 对于稀疏的图,邻接表表示能够极大地节省空间. 以下是图的数据结构的主要部分: struct Vertex{ ElementType element; // ...

  5. 数据结构学习笔记05图 (邻接矩阵 邻接表-->BFS DFS、最短路径)

    数据结构之图 图(Graph) 包含 一组顶点:通常用V (Vertex) 表示顶点集合 一组边:通常用E (Edge) 表示边的集合 边是顶点对:(v, w) ∈E ,其中v, w ∈ V 有向边& ...

  6. 数据结构和算法总结(一):广度优先搜索BFS和深度优先搜索DFS

    前言 这几天复习图论算法,觉得BFS和DFS挺重要的,而且应用比较多,故记录一下. 广度优先搜索 有一个有向图如图a 图a 广度优先搜索的策略是: 从起始点开始遍历其邻接的节点,由此向外不断扩散. 1 ...

  7. 数据结构:关键路径,利用DFS遍历每一条关键路径JAVA语言实现

    这是我们学校做的数据结构课设,要求分别输出关键路径,我查遍资料java版的只能找到关键路径,但是无法分别输出关键路径 c++有可以分别输出的,所以在明白思想后自己写了一个java版的 函数带有输入函数 ...

  8. [数据结构]图的DFS和BFS的两种实现方式

    深度优先搜索 深度优先搜索,我们以无向图为例. 图的深度优先搜索(Depth First Search),和树的先序遍历比较类似. 它的思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发, ...

  9. hdu 1022:Train Problem I(数据结构,栈,递归,dfs)

    Train Problem I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)T ...

随机推荐

  1. Restframework框架总结及restful规范

    1. django rest framework框架的作用? 帮助开发者可以快速开发出遵循restful规范的API 2. django rest framework框架都有哪些组件(10)? -版本 ...

  2. Question20180106 Java环境变量的配置及为什么要配置环境变量

    Question 1  Java环境变量的配置及为什么要配置环境变量 Q1.1为什么要配置环境变量 在学习JAVA的过程中,涉及到多个环境变量(environment variable)的概念,如PA ...

  3. 自动化维护任务 – Automated Maintenance Task (转)

    1. Oracle有三个已定义好的automated maintenance tasks. Automatic Optimizer Statistics Collection—用于收集各种数据库对象的 ...

  4. java面向对象编程思想的理解

    1.我们总说java是一门面向对象编程的语言,那什么是面向对象呢? 我是这样理解的,对象是事物存在的实体,如,猪.狗,花早等都是对象,对象由两部分组成.面向对象编程的三大特点:继承,多态,类是封装对象 ...

  5. Sass 基础(七)

    Sass Maps 的函数-map-remove($map,$key),keywords($args) map-remove($map,$key) map-remove($map,$key)函数是用来 ...

  6. JS数组去重的十种方法(转载)

    重点:博客原文出自http://www.cnblogs.com/yuanbo88/p/7345331.html.我只是想给自己做个笔记.感谢敲代码的怪蜀黍做的贡献. Methods 1: 思路:定义一 ...

  7. mac 开启mysql日志

    step1: 进入终端进入mysql: step2 : 开启mysql日志 step3 : 查看mysql的日志文件所在位置 step4 : 在终端中用tail -f 命令打开该日志文件:

  8. notepad++实现python运行

    一.先确保windows电脑上先安装python解释器 方法参考:https://www.cnblogs.com/hepeilinnow/p/9727922.html 二.打开notepad++,写一 ...

  9. python元组操作

    元组:(tuple)元素不可被修改,不能被增加或者删除 一般写元组的时候,建议在最后加上一个逗号 可以索引取值    可以切片取值 元组一级元素不可被修改,但是二级及以后可以被修改 count() 获 ...

  10. php 使用当前时间点进行时间范围查询

    /** * 判断是否是吃早饭时间 */ $nowtime = time(); $start = strtotime('8:30:00'); $end = strtotime('9:30:00'); i ...