第三章 图与遍历算法
§1 图的基本概念和术语
null 无向图(undirected graph)
哥尼斯堡七桥
Euler 图
无向图,简称图,是一个用线(边)连接在一起的节点(顶点)的集合。严
格地说,图是一个三元组 G=( V, E, I ), 其中,V 是顶点的集合,E 是边的集
合,而 I 是关联关系,它指明了 E 中的每条边与 V 中的每个顶点之间的关联关
系:每条边必定连接两个而且只有两个顶点,它们称为该边的端点。连接顶点v
的边的条数称为v的度,记做d(v). 图G=( V, E, I )中顶点的度与边数有如下
关系
||2)( Evd
Vv
=
∑
∈
(3.1.1)
由公式(1)可知, 图中奇度顶点的个数一定是偶数 。
没有重边的图称为简单图,n 阶完全图是指具有 n 个顶点而且每两个顶点之
间都有边连接的简单图,这样的图的边数为n(n-1)/2.以下是1-4阶的完全图:
另一类特殊的图是偶图(也叫二分图) ,它的顶点集分成两部分 V
1
,V
2
,同
一部分的顶点之间不相连(没有边连接) 。
一个偶图
设图 G 的顶点集是 V={v
1
, v
2
, …,v
n
},边集是 E={e
1
, e
2
, …,e
m
},则顶点
与顶点之间的邻接关系、顶点与边之间的邻接关系可以列表如下:
v
1
v
2
… v
n
v
1
a
11
a
12
…
a
1n
邻接矩阵
v
2
a
21
a
22
… a
2n
A=(a
ij
)
n×n
。 。 。 … 。
。 。 。 … 。
。 。 。
… 。
v
n
a
n1
a
n2
…
a
nn
其中
?
?
?
=
otherwise
adjacentarevandvif
a
ji
ij
0
1
e
1
e
2
… e
m
v
1
c
11
c
12
…
c
1m
关联矩阵
v
2
c
21
c
22
… c
2m
M=(c
ij
)
n×m
。 。 。 … 。
。 。 。 … 。
。 。 。 … 。
v
n
c
n1
c
n2
…
c
nm
K
1
K
4
K
3
K
2
V
1
V
2
其中
?
?
?
=
otherwise
eofpoendtheofoneisvif
c
ji
ij
0
1 ints
图3-1-1
一个具有7
5 个顶点的图
图3-1的邻接矩阵为 A,关联矩阵是 M :
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
=
0110000
1010000
1101000
0010000
0000001
0000001
0000110
A
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
=
1100000
1010000
0111000
0001000
0000110
0000011
0000101
M
图的另一个重要概念是路径,途径、迹、路。
途径 :顶点与边交叉出现的序列
v
0
e
1
v
1
e
2
v
2
··· e
l
v
l
(3.1.2)
其中e
i
的端点是v
i-1
和v
i
。 迹 是指边不重复的途径,而顶点不重复的途径称为
路 。路是要求最严的。
一条途径:
v
1
e
1
v
2
e
10
v
4
e
5
v
3
e
9
v
1
e
1
v
2
e
2
v
8
一条迹:
v
1
e
1
v
2
e
10
v
4
e
5
v
3
e
9
v
1
e
4
v
7
一条路:
v
1
e
1
v
2
e
10
v
4
e
5
v
3
e
8
v
5
e
12
v
7
图3-1-2 立方体 H
起点和终点重合的途径称为闭途径,起点和终点重合的迹称为闭迹,顶点不
V
1
V
2
V
3
V
4
V
5
V
6
V
7
V
8
e
1
e
2
e
3
e
5
e
6
e
7
e
8
e
9 e
10
e
11e12
e
4
e
5
e
6
e
7
e
4
6
4
5
7
e
1
1
e
2
e
3
3
2
重复的闭迹称为 圈 。没有圈的图称为 森林 。如果存在一条以u为起点、v为终点
的途径,则说顶点u可达顶点v。如果图G中任何两个顶点之间都是可达的,则
说图G是连通。如果图G不是连通的,则其必能分成几个连通分支。图3-2是连
通的,而图3-1不是连通的,它有两个连通分支。
不含圈的连通图称为 树 ,森林的连通分支是树,也就 是说,森林是由树组
成的。 森林即是不含圈的图。 适当去掉连通图中的一些边后, 会得到一个不含圈、
而且包含所有顶点的连通图,它是一棵树,称为原图的 生成树 。生成树是其所在
的图中边数最少的连通生成子图,因此,具有 n 个顶点的连通图的边数至少是
n-1 .不连通图没有生成树,但有生成森林。如果不连通图 G 有 k 个连通分支,
则G的边数至少是n-k.
定理3.1.1 如果G是具有n个顶点、m条边的图,下列结论成立:
1. 若G是树, 则m = n-1;
2. 若G是连通图,而且满足m = n-1,则G是树;
3. 若G不包含圈,而且满足m = n-1,则G是树;
4. 若G是由k棵树构成的森林,则m = n-k ;
5. 若G有k 个连通分支,而且满足m = n-k,则G是森林。
null 有向图
图3-1-3
一个有向图及其
双向连通分支
2 3
4
5 6
1
单向
单向
单向单向
街道 1 街道 3街道 2
大街 1
大街 2
2 3
4 5 6
1
描述单行道系统就不能用无向图,因 为它不能指明各条路的方向。所谓有
向图实际上是在无向图的基础上进一步指定各条边的方向。在有向图中,顶点v
的出度是指由顶点 v 出发的有向边的条数,记做 d
+
(v);而入度则是指向顶点 v
的有向边的条数, 记做 d
-
(v)。此外也有顶点度的概念,它是该顶点的出度与入
度的和: d(v)=d
+
(v)+d
-
(v)。
在有向图中,许多概念都可以通过无向图的相关概念加“有向”二字得到,
如 有向边、有向途径、有向迹、有向路、有向圈,等等。有向图和无向图可以
相互转化:将一个无向图的每条边都规定方向后,即得到有向图,其称为原无向
图的一个定向图;将一个有向图的各条有向边的方向去掉,即得到一个无向图,
其称为原有向图的基础图。
有向图中也有一些概念不能由无向图通过简单地附加“有向”一词而得到。
如,连通,有向图D说是连通的是指其基础图是连通的。如果D中任意两个顶点
都是可达的,则说有向图 D 是双向连通的(或叫强连通) 。这里,顶点 u 可达顶
点v是指存在一条以u为起点、v为终点的有向路。这里的起点、终点不能互为
替换。有向图3-3就是连通的,但不是双向连通的,因为从任何顶点出发,都没
有到达顶点 3 的有向途径。不是双向连通的有向图可以分解成几个双向连通分
支。图3-3共有5个双向连通分支,分别用不同的颜色标出。
null 加权图与网络
一般的加权图是指对图的每条边e赋予一个实数值W(e)。如架设连接各城
镇的交通路网,需考虑各段线路的修建费用;在运输网络中要考虑网络各段线路
的容量,等等。
图3-1-4 一个交通路网
网络是一个这样的加权有向图,其指明了 收点集 和 发点集, 在图3-5中分别
用粉色和黄色标出。其余的顶点称为内点(绿色) 。
6
26
3
7 2
3
1
3
6
8
3
图3-1-5 网络
§2 图的遍历(搜索)算法
null 二叉树的遍历
一棵完全的二叉树 一棵完整的二叉树
对于二叉树的搜索按照子树的根的优先访问次序分为:先跟次序搜索、中根
次序搜索和后跟次序搜索三种方式,如下图所示。
2
V
2
V
4
V
1
V
3
X
1
X
2
Y
1
Y
2
Y
3
1
2
3
2
6 5
1
45
3
6
4
2
4
1
A
B C
D E F G
H I J
A
B C
D E F G
H I K M
O NL
1
A
B
D
F
H
G
I
C
E
4
3
5
6
7
8
9
2
中根次序搜索
1
A
B
D
F
H
G
I
C
E
4
2
3
7
6
9
8
5
后根次序搜索
4
A
B
D
F
H
G
I
C
E
5
6
7
2
8
1
9
3
先根次序搜索
二叉树的中根次序遍历算法伪代码
InOder(T) // T是一棵二叉树,T的每个顶点有三个信息段:
//Lchild,Data,Rchild
if T≠0 then
InOrder(Lchild(T));
Visit(T);
InOrder(Rchild(T));
endif
endInOrder
二叉树的先根次序遍历算法伪代码
PreOder(T) // T是一棵二叉树,T的每个顶点有三个信息段:
//Lchild,Data,Rchild
if T≠0 then
Visit(T);
PreOrder(Lchild(T));
PreOrder(Rchild(T));
endif
endPreOrder
二叉树的后根次序遍历算法伪代码
PostOder(T) // T是一棵二叉树,T的每个顶点有三个信息段:
//Lchild,Data,Rchild
if T≠0 then
PostOrder(Lchild(T));
PostOrder(Rchild(T));
Visit(T);
endif
endPostOrder
null 一般树的遍历算法
我们可以将二叉树的父与子之间的关系推广到树上,这样每个父亲的儿子
可以有许多个,而且可以排出顺序。于是,关于二叉树的三种遍历算法完全可以
移置到一般的树上来。
树的先根次序遍历算法 :
i. 若T为空,则返回;
ii. 访问T的根;
iii. 按树的先根次序遍历T的第一棵子树;
iv. 按树的先根次序依次遍历T的其余子树。
树的中根次序遍历算法 :
v. 若T为空,则返回;
vi. 按树的中根次序遍历T的第一棵子树;
vii. 访问T的根;
viii. 按树的中根次序依次遍历T的其余子树。
树的后根次序遍历算法 :
ix. 若T为空,则返回;
x. 按树的先根次序遍历T的第一棵子树;
xi. 按树的先根次序依次遍历T的其余子树。
xii. 访问T的根;
null 一般图的遍历
无论是二叉树还是一般的树,由于其不含有圈,所以属于同根的各个子树之
间是相互独立的(树中去掉一点以及与之关联的所有边后,出现若干棵树,均称
之为以这点为根的子树) 。因此遍历过程是对各个子树的分别遍历和对根遍历以
及把这些遍历有机地组合起来。一般的图没有这种独立性,所以上述方法不能施
行。但是,上述方法的思想可以借鉴,于是产生了深度优先搜索方法和宽度优先
搜索方法。
问题 : 在一个给定的图 G=(V,E)中是否存在一条起于顶点 v 而终于顶点 u
的路径?确定与某一起点v有路相通的所有顶点 。
1。宽度优先搜索算法(BFS)
开始:起点v和一个空的待访队列Q。
从顶点 v 开始访问,并将 v 标记为已访问的顶点。然后顺序(这个顺序通
常是图的顶点存储顺序)搜索邻接于顶点v的未被访问的顶点,并把这些顶点依
次放在待访队列Q的尾部。
用队列Q的首元素u替换v(并从队列Q中去掉首元素u) ,重复以上过程,
直到队列Q空为止。
图的宽度优先搜索算法伪代码
BFS(v) //宽度优先搜索G,从顶点v开始执行
// 所有已搜索的顶点i都标记为Visited(i)=1.
//Visited的初始分量值全为0
1. Visited(v)=1;
2. Q=[];//将Q初始化为只含有一个元素v的队列
3. while Q非空 do
4. u=DelHead(Q);
5. for 邻接于u的所有顶点w do
6. if Visited(w)=0 then
7. AddQ(w,Q); //将w放于队列Q之尾
8. Visited(w)=1;
9. endif
10. endfor
11. endwhile
12. end BFS
这里调用了两个函数:AddQ(w,Q)是将 w 放于队列 Q 之尾;DelHead(Q)是从队列
Q取第一个顶点,并将其从Q中删除。
定理3.2.1 图G的宽度优先搜索算法能够访问G中由v可能到达的所有顶
点。如果记t( ν ,e)和s( ν ,e)为算法BFS在任意一个具有 ν 个顶点和 ε 条边的图
G上所花的最大时间和最大空间。则
t(ν ,ε )=Θ( ν +ε ), s(ν ,ε )=Θ( ν ) 当G由邻接链表表示时;
t(ν ,ε )=Θ( ν
2
), s(ν ,ε )=Θ( ν ) 当G由邻接矩阵表示时;
证明略。
由定理2可知, 宽度优先搜索算法能够遍历图G的包含v的连通分支中的所
有顶点。对于不连通的图,可以通过在每个连通分支中选出一个顶点作为起点,
应用宽度搜索算法于每个连通分支,即可遍历该图的所有顶点。
图的宽度优先遍历算法伪代码
BFT(G, ν ) //图G的宽度优先遍历
for i from 1 to ν do
Visited(i)=0; //将所有的顶点标记为未被访问
endfor
for i from 1 to ν do
if Visited(i)==0 then BFS(i); endif
endfor
end BST
关于BST算法的时间和空间复杂性与BFS同样估计,略。
如果G是连通图,则G有生成树。注意到BFS算法中,由5~10行,将所有
邻接于顶点u但未被访问的顶点w添加到待访队列中。 如果在添加w的同时将边
(u,w)收集起来,那么算法结束时,所有这些边将形成图 G 的一棵生成树。称为
图 G 的宽度优先生成树。为此,在 BFS 算法的第 1 行增加语句 T={};在第 7 行
增加语句T=T∪{(u,w)}即可。
图G及其邻接链表
2。图的深度优先搜索
深度优先搜索是沿着顶点的邻点一直搜索下去,直到当前被搜索的顶点不
再有未被访问的邻点为止,此时,从当前辈搜索的顶点原路返回到在它之前被搜
索的访问的顶点,并以此顶点作为当前被搜索顶点。继续这样的过程,直至不能
B C 0
A D E 0
D E F G 0
A F G 0
C H 0
B H 0
B H 0
C H 0
A
B
C
D
E
F
G
H
A
B C
E FD G
H
1
2 7
3
5
6
8
4
A
B C
E FD G
H
图的深度优先搜索
1
2 3
4
5
6
7
8
A
B C
E FD G
H
图的宽度优先搜索
执行为止。
图的深度优先搜索算法伪代码
DFS(v) //访问由v到达的所有顶点
1. Visited(v)=1;
2. for邻接于v的每个顶点w do
3. if Visited(w)=0 then
4. DFS(w);
5. endif
6. endfor
7.end DFS
§3 双连通与网络可靠性
通信网络的抽象模型可以是一个无向图: 顶点代表通信站, 边代表通信线路。
下面的两个图代表两个通信网络:
直观可知,图3-8的通信网络可靠性较高,因为即使有一个网站出现问题,
其他网站之间的通信仍能够继续进行。而图3-9所示的网络则不能,只要网站B
发生故障,位于其左右两部分的网站之间就无法连通了。在图中,象B点这样的
顶点称为割点, 。
定义 3.3.1 连通无向图 G 中的顶点 v 称为割点,如果在 G 中去掉 v 及其关
联的边,剩下的图就不再连通。没有割点的连通图称为双连通的(也称为块、2
-连通等) 。图G中极大的双连通子图称为G的一个双连通分支。
在图3-9中除了B以外,C和E也都是割点。这个图有5个双连通分支(参
见图3-10).图3-8是双连通的。
A
B
E
D
F
G
C
E
I
J
D
C
A
F
GB
H
图3-3-1 一个双连通图 图3-3-2 一个非双连通图
就通信网络而言,当然希望没有割点,如果现有的网络有割点,则设法增加
一些线路(当然希望尽量少的增加) ,使之成为双连通的。
添加边的算法:
E1: for每个割点u do
E2: 设B
1
,B
2
,… ,B
k
,是包含割点u的全部双连通分支
E3: 设 V
i
是B
i
的一个顶点,且V
i
≠u,1≤i≤k。
E4: 将(V
i
, V
i+1
)添加到G,1≤i≤k。
E5: endfor
问题 : 设计算法测试一个连通图是否双连通;若不是,算法将识别出割点。
解决方法:以深度优先遍历算法为基础,加以割点识别步骤。
当采用深度优先遍历算法时,顶点 v 被访问的序数称为 v 的深度优先数,记
做DFN(v).
按照深度优先树将图中的顶点分层,使得上层是下层的祖先,而同层之间是
I
C
E
F
J
C
E
GB
H
D
C
A
B
图3-3-3 所示图的5个双连通分支
E
I
J
D
C
A
F
GB
H
1
8
2
a
7
6
c
d
9
3
4
5
10
b
E
I
J
D
C
A
F
GB
H
添加边以形成双连通图 深度优先搜索树
兄弟关系:
深度优先树的性质:
1.关于深度优先树T,图G的每一条边(u,v)的两个端点u、v
之间,或u是v 的祖先, 或v是u 的祖先。
2.树 T 的根顶点是图 G 的割点当且仅当其在 T 中至少有两个儿子;不是
T的根的顶点u不是G的割点当且仅当u在T中的每个儿子w都至少有一个子孙
(或 w 本身)关联着一条边 e,e 的另一个端点是 u 的某个祖先(e 一定是树 T
的余边) 。
根据性质2,深度优先树T的非根顶 点u是G 的割点当且仅当u至少有一
个儿子 w,w 及其子孙都不与 u 的任何祖先相邻。注意到 u 的深度优先数一定小
于其子孙的深度优先数, 所以深度优先数DFN并不能反映一个顶点是否是割点的
情况。为此,我们递归地定义最低深度优先数L:
定义3.3.2 顶点u 的最低深度优先数L(u)定义为
L(u)=min{DFN(u),min{L(w)|w是u的儿子},
Min{DFN(x)|(u,x)是T的余边}}
可见,如果L(u)≠DFN(u),则必定L(u)< DFN(u),因而L(u)是u通过一条
子孙路径且至多后跟一条T的余边所可能到达的最低深度优先数。 如果u不是根
顶点,则 u 是图 G 的割点当且仅当 u 有某个儿子 w,其最低深度优先数不小于 u
的深度优先数:L(w)≥DFN(u)。看来要想识别图G的所有割点,需先获得深度优
先生成树T,然后按后根优先次序遍历T计算出各个顶点的最低深度优先数。但
是,从函数L的定义可知这两步工作可以同时完成。
计算DF N和L的算法伪代码
A
D
BI
E
J
C
G
F
H
8
d
1
a
b
c6
4 5
1
1
6
6
1
DFNL(u,v) //一个深度优先搜索算法,u是开始顶点,
//在深度优先树中,若u有父亲,则v即是。数组
//DFN初始化为0,num初始化为1,n是图G的顶
//点数。
global DFN[n],L[n],num,n
1. DFN(u)=num; L(u)=num; num=num+1;
2. for 每个邻接于u的顶点w do
3. If DFN(w)==0 then DFNL(w,u) //还未访问w
4. L(u)=min(L(u),L(w));
5. else
6. if w≠v then L(u)=min(L(u),DFN(w)); endif
7. endif
8. endfor
9.end DFNL
为了得到图 G 的双连通分支,需对 DFNL 作一些修改。注意到,在第 3 行调
用 DFNL 时,如果出现情况:L(w)≥DFN(u),就可以断定 u 或是 T 的根,或是图
G的割点。不论那种情况,都将边(u,w)和对DFNL这次调用期间遇到的所有树边
和余边加在一起(除了包含在子树 w 中其它双连通分支中的边以外) ,就构成图
G的一个双连通分支。鉴于此,将DFNL做如下修改:
null 引进一个存放边的全程栈S;
null 在2到3 行之间加:
2.1 if v≠w and DFN(w)<DFN(u) then
2.2 将(u,w)加到S的顶部;
2.3 endif
null 在3到4 行之间加下列语句:
3.1 if L(w)≥DFN(u) then
3.2 print(’new biconnected component’);
3.3 loop
3.4 从栈S的顶部删去一条边;
3.5 设这条边是(x,y)
3.6 print(‘(‘,x,’,’,y,’)’);
3.7 until ((x,y)=(u,w) or(x,y)=(w,u));
3.8 endif
如果G是有n个顶点m条边连通图,且G有邻接链表表示,
那么 DFN 的计算时间是 O(n+m).一旦算出 L[1:n],G 的割点就能在 O(n)时间识别
出来,因此,识别全部割点总的时间不超过 O(n+m).当 DFNL 增加了上述语句之
后,其计算时间仍然是O(n+m).
定理 3.3.1 设 G 是至少有两个顶点的连通图,则算法 DFN 增加了语句
2.1-2.3和3.1-3.8的算法DFNL能正确生成G的全部双连通分支。
证明:略。