本系列文章为浙江大学陈越、何钦铭数据结构学习笔记,前面的系列文章链接如下:
数据结构基础:P1-基本概念
数据结构基础:P2.1-线性结构—>线性表
数据结构基础:P2.2-线性结构—>堆栈
数据结构基础:P2.3-线性结构—>队列
数据结构基础:P2.4-线性结构—>应用实例:多项式加法运算
数据结构基础:P2.5-线性结构—>应用实例:多项式乘法与加法运算-C实现
数据结构基础:P3.1-树(一)—>树与树的表示
数据结构基础:P3.2-树(一)—>二叉树及存储结构
数据结构基础:P3.3-树(一)—>二叉树的遍历
数据结构基础:P3.4-树(一)—>小白专场:树的同构-C语言实现
数据结构基础:P4.1-树(二)—>二叉搜索树
数据结构基础:P4.2-树(二)—>二叉平衡树
数据结构基础:P4.3-树(二)—>小白专场:是否同一棵二叉搜索树-C实现
数据结构基础:P4.4-树(二)—>线性结构之习题选讲:逆转链表
数据结构基础:P5.1-树(三)—>堆
数据结构基础:P5.2-树(三)—>哈夫曼树与哈夫曼编码
数据结构基础:P5.3-树(三)—>集合及运算
数据结构基础:P5.4-树(三)—>入门专场:堆中的路径
数据结构基础:P5.5-树(三)—>入门专场:File Transfer
数据结构基础:P6.1-图(一)—>什么是图
文章目录
- 前言
- 一、深度优先搜索(Depth First Search, DFS)
- 二、广度优先搜索(Breadth First Search, BFS)
- 三、为什么需要两种遍历
- 四、图不连通怎么样
- C语言代码:DFS-邻接表存储
- C语言代码:BFS-邻接矩阵存储
- 小测验
前言
遍历这个词在图里面的意思跟在树里面的意思是一样的。图的遍历就是指把图里面每一个顶点访问一遍,而且不能有重复的访问。首先,一个问题是说我们干嘛要做这件事呢?你不要小看这件事,看上去非常简单,它可以用来解决很多有趣的问题,我们后面会用例子给大家说明。在这先给大家介绍两种非常典型的遍历的方法一种叫做 DFS,一种叫做 BFS。
一、深度优先搜索(Depth First Search, DFS)
所谓 DFS 是英文 Depth First Search 的缩写,它指的是深度优先搜索。要讲清楚深度优先搜索是怎么做的,我们可以先通过一个迷宫的例子来看一下。
刚才演示的就是一个深度优先搜索的过程。这里头我们注意到有一个特点就是,当一个结点周围所有的灯都是亮的以后,它一定是原路返回。原路返回的这个行为在程序里面其实就对应是堆栈的出栈这个行为。下面就是我们深度优先搜索的一个算法的伪代码。这个程序看上去略眼熟,如果你去想一下树,这个实际上相当于是树的先序遍历。整个DFS其实是树的先序遍历的一个推广。
void DFS ( Vertex V )
{
visited[ V ] = true;
for ( V 的每个邻接点 W )
if ( !visited[ W ] )
DFS( W );
}
接下来我们的问题就是,如果给定的这个图里面有n
个顶点e
条边,那么DFS的时间复杂度是多少呢?
二、广度优先搜索(Breadth First Search, BFS)
说完了DFS,我们再来看这个广度优先搜索。广度优先搜索在树里面就相当于是一个层序遍历,所以我们先来回顾一下树的层序遍历是怎么做的。
对应的伪代码如下:
void BFS ( Vertex V )
{
visited[V] = true; //图的入口先置为已访问
Enqueue(V, Q); //入队
while(!IsEmpty(Q)){
V = Dequeue(Q); //出队
for ( V 的每个邻接点 W )
if ( !visited[W] ) {
visited[W] = true;
Enqueue(W, Q);
}
}
}
对于有N个顶点、E条边的图,BFS的复杂度是多少呢?
三、为什么需要两种遍历
那前面我们介绍了两种不同的遍历方法,这里有一个很自然的问题就是:为什么我需要两种不同的遍历呢?所谓遍历,不过是把图里面每一个顶点访问一次,那我其实只要知道其中一种就可以了,为什么我需要知道两种呢?其实这两种遍历各有它们不一样的特点,它们的特点是什么样的呢?我们来通过一个迷宫的例子给大家演示一下:
所以很明显的我们会看到,如果我们在这个问题里头用广度优先搜索会比深度优先搜索效果要好很多。于是你要想了,既然如此的话那我一直都用广度优先搜索就好了,我还需要深度优先搜索干什么呢?其实这么说也是不公平的,在这个问题里面,广度优先搜索表现的好是因为我把迷宫的出口设在了这。如果把出口换到另外一个位置,可能DFS效果就会更好了。
四、图不连通怎么样
前面我们讲了两种图的遍历的方法,你要注意到不管是哪一种方法,它都是从一个结点出发,然后沿着某一条边往下走的。也就意味着他访问过的所有的结点互相之间都是有直接或者间接的边去连通的。如果这个问题里面它有另外一个完全跟谁都不挨着的结点怎么办呢?它怎么能做到遍历呢?你如果用一次深度优先或者广度优先遍历,你肯定会丢掉一些孤立的结点,那么就是我们的下一个问题,图不连通的时候怎么办呢?要想清楚这个问题,首先我们得知道什么是连通。
连通的相关概念
不连通的图
我们这里说的连通分量指的是无向图,对于有向图是什么概念呢?
对于连通图,之前写的遍历程序如下。
void DFS ( Vertex V )
{
visited[ V ] = true;
for ( V 的每个邻接点 W )
if ( !visited[ W ] )
DFS( W );
}
对于不连通的图,如果要遍历所有结点,解决办法很简单。就是我要把所有的连通分量都列出来,然后对每个分量进行遍历
void ListComponents ( Graph G )
{
for ( each V in G )
if ( !visited[V] ) {
DFS( V ); /*or BFS( V )*/
}
C语言代码:DFS-邻接表存储
/* 邻接表存储的图 - DFS */
void Visit( Vertex V )
{
printf("正在访问顶点%d\n", V);
}
/* Visited[]为全局变量,已经初始化为false */
void DFS( LGraph Graph, Vertex V, void (*Visit)(Vertex) )
{ /* 以V为出发点对邻接表存储的图Graph进行DFS搜索 */
PtrToAdjVNode W;
Visit( V ); /* 访问第V个顶点 */
Visited[V] = true; /* 标记V已访问 */
for( W=Graph->G[V].FirstEdge; W; W=W->Next ) /* 对V的每个邻接点W->AdjV */
if ( !Visited[W->AdjV] ) /* 若W->AdjV未被访问 */
DFS( Graph, W->AdjV, Visit ); /* 则递归访问之 */
}
C语言代码:BFS-邻接矩阵存储
/* 邻接矩阵存储的图 - BFS */
/* IsEdge(Graph, V, W)检查<V, W>是否图Graph中的一条边,即W是否V的邻接点。 */
/* 此函数根据图的不同类型要做不同的实现,关键取决于对不存在的边的表示方法。*/
/* 例如对有权图, 如果不存在的边被初始化为INFINITY, 则函数实现如下: */
bool IsEdge( MGraph Graph, Vertex V, Vertex W )
{
return Graph->G[V][W]<INFINITY ? true : false;
}
/* Visited[]为全局变量,已经初始化为false */
void BFS ( MGraph Graph, Vertex S, void (*Visit)(Vertex) )
{ /* 以S为出发点对邻接矩阵存储的图Graph进行BFS搜索 */
Queue Q;
Vertex V, W;
Q = CreateQueue( MaxSize ); /* 创建空队列, MaxSize为外部定义的常数 */
/* 访问顶点S:此处可根据具体访问需要改写 */
Visit( S );
Visited[S] = true; /* 标记S已访问 */
AddQ(Q, S); /* S入队列 */
while ( !IsEmpty(Q) ) {
V = DeleteQ(Q); /* 弹出V */
for( W=0; W<Graph->Nv; W++ ) /* 对图中的每个顶点W */
/* 若W是V的邻接点并且未访问过 */
if ( !Visited[W] && IsEdge(Graph, V, W) ) {
/* 访问顶点W */
Visit( W );
Visited[W] = true; /* 标记W已访问 */
AddQ(Q, W); /* W入队列 */
}
} /* while结束*/
}
小测验
1、已知一个图如下图所示,从顶点a出发按深度优先搜索法进行遍历,则可能得到的一种顶点序列为
A. a,e,b,c,f,d
B. a,b,e,c,d,f
C. a,c,f,e,b,d
D. a,e,d,f,c,b
答案:D
2、已知一个图如下图所示,从顶点a出发按广度优先搜索法进行遍历,则可能得到的一种顶点序列为
A. a,b,c,e,d,f
B. a,b,c,e,f,d
C. a,e,b,c,f,d
D. a,c,f,d,e,b
答案:B
3、具有个N(>0)顶点的无向图至多有多少个连通分量
A. 0
B. 1
C. N
D. N-1
答案:D
4、如果从无向图的任一顶点出发进行一次深度优先搜索可访问所有顶点,则该图一定是
A. 有回路的图
B. 完全图
C. 连通图
D. 一棵树
答案:C
5、具有N(>0)个顶点的无向图至少有多少个连通分量
A. 0
B. 1
C. N
D. N-1
答案:B