第 7 章 图

87

description

第 7 章 图. 7.1 图的定义与基本术语 7.2 图的存储结构 7.3 图的遍历 7.4 图的连通性问题 7.5 有向无环图的应用 7.6 最短路径. 7.1 图的定义与基本术语. 7.1.1 图的定义. 图 (Graph) 是一种网状数据结构, 其形式化定义如下:  Graph= ( V , R ) V={x|x∈DataObject} R={VR} VR={|P ( x , y )∧( x , y∈V ) } - PowerPoint PPT Presentation

Transcript of 第 7 章 图

Page 1: 第 7 章 图
Page 2: 第 7 章 图

第 7 章 图 7.1 图的定义与基本术语

7.2 图的存储结构

7.3 图的遍历

7.4 图的连通性问题

7.5 有向无环图的应用

7.6 最短路径

Page 3: 第 7 章 图

7.1 图的定义与基本术语

图 (Graph) 是一种网状数据结构, 其形式化定义如下:

Graph= ( V , R )

V={x|x DataObject}∈

R={VR}

VR={<x , y>|P ( x , y )∧( x , y V∈ ) }

DataObject 为一个集合,该集合中的所有元素具有相同的特性。 V 中的数据元素通常称为顶点( vertex ), VR 是两个顶点之间关系的集合。 P ( x , y )表示 x 和 y 之间有特定的关联属性 P 。

7.1.1 图的定义

Page 4: 第 7 章 图

若 <x , y> VR∈ ,则 <x , y> 表示从顶点 x 到顶点 y 的一条弧( arc ),并称 x 为弧尾( tail )或起始点,称 y 为弧头( hea

d )或终端点,此时图中的边是有方向的,称这样的图为有向图。

若 <x , y> VR∈ , 必有 <y , x> VR∈ ,即 VR 是对称关系,这时以无序对( x , y )来代替两个有序对,表示 x 和 y 之间的一条边( edge ),此时的图称为无向图。

Page 5: 第 7 章 图

图 7.1 有向图、无向图示例

1

3

2

4

G11

4

2

5

G2

(a ) G1ÊÇÓÐÏòͼ (b ) G2ÊÇÎÞÏòͼ

3 ͼ7.1

Page 6: 第 7 章 图

ADT Graph

数据对象 V : 一个集合,该集合中的所有元素具有相同的特性。

数据关系 R : R={VR}

VR={<x , y>|P ( x , y )∧( x , y V∈ ) }

基本操作:

( 1 ) CreateGraph ( G ): 创建图 G 。

( 2 ) DestoryGraph ( G ): 销毁图 G 。

( 3 ) LocateVertex ( G , v ):确定顶点 v 在图 G 中的位置。 若图 G 中没有顶点 v ,则函数值为“空”。

Page 7: 第 7 章 图

( 4 ) GetVertex ( G , i ): 取出图 G 中的第 i 个顶点的值。 若 i 大于图 G 中顶点数,则函数值为“空”。

( 5 ) FirstAdjVertex ( G , v ):求图 G 中顶点 v 的第一个邻接点。 若 v 无邻接点或图 G 中无顶点 v ,则函数值为“空”。

( 6 ) NextAdjVertex ( G , v , w ):已知 w 是图 G 中顶点 v 的某个邻接点,求顶点 v 的下一个邻接点(紧跟在 w

后面)。 若 w 是 v 的最后一个邻接点, 则函数值为“空”。

( 7 ) InsertVertex ( G , u ):在图 G 中增加一个顶点u 。

Page 8: 第 7 章 图

( 8 ) DeleteVertex ( G , v ):删除图 G 的顶点 v 及与顶点 v 相关联的弧。

( 9 ) InsertArc ( G , v , w ):在图 G 中增加一条从顶点 v 到顶点 w 的弧。

( 10 ) DeleteArc ( G , v , w ): 删除图 G 中从顶点 v到顶点 w 的弧。

( 11 ) TraverseGraph ( G ): 按照某种次序, 对图 G 的每个结点访问一次且仅访问一次。

Page 9: 第 7 章 图

7.1.2 基本术语 1. 完全图、稀疏图与稠密图

我们设 n 表示图中顶点的个数,用 e 表示图中边或弧的数目, 并且不考虑图中每个顶点到其自身的边或弧。即若 <vi , vj

> VR∈ , 则 vi≠vj 。对于无向图而言,其边数 e 的取值范围是 0~n ( n-1 ) /2 。 我们称有 n ( n-1 ) /2 条边(图中每个顶点和其余 n-1 个顶点都有边相连)的无向图为无向完全图。对于有向图而言,其边数 e 的取值范围是 0~n ( n-1 )。 我们称有 n( n-1 )条边(图中每个顶点和其余 n-1 个顶点都有弧相连)的有向图为有向完全图。

对于有很少条边的图( e<n logn )称为稀疏图, 反之称为稠密图。

Page 10: 第 7 章 图

2. 子图

设有两个图 G= ( V , {E} )和图 G′= ( V′ , {E′} ) , 若 V′ V

且 E′ E, 则称图 G′ 为 G 的子图。

图 7.2 图的子图示例

1 1

3

1

3 4 3 4

1 2

(a ) G1µÄ×Óͼ

1 1 2

5

3

1 2

54

1 2

54

3

(b ) G2µÄ×Óͼ

Page 11: 第 7 章 图

3. 邻接点

对于无向图 G= ( V , {E} ),如果边( v , v′ )∈ E ,

则称顶点 v , v′ 互为邻接点,即 v , v′ 相邻接。边( v ,

v′ )依附于顶点 v 和 v′ ,或者说边( v , v′ )与顶点 v 和 v′

相关联。 对于有向图 G= ( V , {A} )而言,若弧 <v , v′>

A∈ , 则称顶点 v 邻接到顶点 v′ ,顶点 v′ 邻接自顶点 v ,或

者说弧 <v , v′> 与顶点 v 和 v′ 相关联。

Page 12: 第 7 章 图

4. 度、入度和出度

对于无向图而言,顶点 v 的度是指和 v 相关联边的数目,记作( v )。例如:图 7.1 中 G2 中顶点 v3 的度是 3 , v1 的度是 2 ;在有向图中顶点 v 的度有出度和入度两部分,其中以顶点 v 为弧头的弧的数目成为该顶点的入度,记作 ID ( v ),以顶点 v 为弧尾的弧的数目称为该顶点的出度,记作 OD ( v ) , 则顶点 v 的度为 TD ( v )=ID ( v ) + OD ( v )。例如: 图 G1 中顶点 v1 的入度是 ID(v1)=1 ,

出度 OD(v1)=2, 顶点 v1 的度 TD(v1)=ID(v1)+OD(v1)=3 。一般地, 若图G 中有 n 个顶点, e 条边或弧,则图中顶点的度与边的关系如下:

2

)(1

n

iivTD

e

Page 13: 第 7 章 图

5. 权与网

在实际应用中,有时图的边或弧上往往与具有一定意义的数有关,即每一条边都有与它相关的数,称为权,这些权可以表示从一个顶点到另一个顶点的距离或耗费等信息。我们将这种带权的图叫做赋权图或网,如图 7.3 所示。

图 7.3 赋权图示例

1

5

2

4

16

6 319

18

33 14

21 11

6

5

6

ͼ7.3

Page 14: 第 7 章 图

6. 路径与回路

无向图 G= ( V , {E} )中从顶点 v 到 v′ 的路径是一个顶点序列 v

i0 , vi1 , vi2 ,…, vin ,其中( vij-1 , vij )∈ E , 1≤j≤n 。如果图 G

是有向图,则路径也是有向的,顶点序列应满足 <vij-1 , vij> A∈ , 1≤j

≤n 。路径的长度是指路径上经过的弧或边的数目。在一个路径中,若

其第一个顶点和最后一个顶点是相同的,即 v=v′ ,则称该路径为一个

回路或环。若表示路径的顶点序列中的顶点各不相同,则称这样的路

径为简单路径。除了第一个和最后一个顶点外,其余各顶点均不重复

出现的回路为简单回路。

Page 15: 第 7 章 图

7. 连通图

在无向图 G= ( V , {E} )中,若从 v i 到 v j 有路径相通,则

称顶点 vi 与 vj 是连通的。如果对于图中的任意两个顶点 vi 、 vj V∈ ,

vi , vj 都是连通的,则称该无向图 G 为连通图。例如: G2 就是连通

图。无向图中的极大连通子图称为该无向图的连通分量。 在有向图 G

= ( V , {A} )中,若对于每对顶点 vi 、 vj V∈ 且 vi≠vj , 从 vi 到 vj

和 vj 到 vi 都有路径,则称该有向图为强连通图。有向图的极大强连通

子图称作有向图的强连通分量,如图 7.4 所示。

Page 16: 第 7 章 图

图 7.4 图 G1 和 G3 的连通分量示例

A

L

B

M

J

C

F

ED

HG

I K

(a ) ÎÞÏòͼG3

A

L

B

M

J

C

F

ED

HG

I K

(b ) ÎÞÏòͼG3µÄÈý¸öÁ¬Í¨·ÖÁ¿

1

3 4

2

ͼ7.4

(c ) ÓÐÏòͼG1µÄÁ½¸öÁ¬Í¨·ÖÁ¿

Page 17: 第 7 章 图

8. 生成树

图 7.5 G3 的最大连通分量的一棵生成树

A

L M

C

F

B

J

ͼ7.5

Page 18: 第 7 章 图

7.2 图的存储结构7.2.1 邻接矩阵表示法

图的邻接矩阵表示法( Adjacency Matrix )也称作数组表示法。它采用两个数组来表示图:

一个是用于存储顶点信息的一维数组;另一个是用于存储图中顶点之间关联关系的二维数组,这个关联关系数组被称为邻接矩阵。

若图 G 是一个具有 n 个顶点的无权图, G 的邻接矩阵是具有如下性质的 n×n 矩阵 A :

0

1],[ jiA

若 <vi, vj> 或 (vi, vj) VR ∈

反之

Page 19: 第 7 章 图

图 7.6 图 G1 , G2 的邻接矩阵

0 1 1 00 0 0 00 0 0 11 0 0 0

A1=

0 1 0 1 01 0 1 0 10 1 0 1 11 0 1 0 00 1 1 0 0

A2=

若图 G 是一个有 n 个顶点的网,则它的邻接矩阵是具有如下性质的 n×n 矩阵 A :

ijwjiA ],[

若 <vi, vj> 或 (vi, vj) VR ∈

反之

Page 20: 第 7 章 图

例如:图 7.7 就是一个有向网 N 及其邻接矩阵的示例。

图 7.7 有向网及其邻接矩阵

ͼ7.7

v 1

v 2

v 6

v 5

v 4

v 3

5

4

8

5

5

7

9

6

1

2

Page 21: 第 7 章 图

邻接矩阵表示法的 C 语言类型描述如下:

# define MAX -VERTEX-NUM 10 /* 最多顶点个数 */

# define INFINITY 32768 /* 表示极大值, 即∞ */

typedef enum{DG, DN, UDG, UDN} GraphKind; /* 图的种类: DG 表示有向图 , DN 表示有向网 , UDG 表示无向图 , UDN 表示无向网 */

typedef char VertexData; /* 假设顶点数据为字符型 */

typedef struct ArcNode{

AdjType adj; /* 对于无权图,用 1 或 0 表示是否相邻;对带权图,则为权值类型 */

Page 22: 第 7 章 图

OtherInfo info;

} ArcNode;

typedef struct{

VertexData vexs[MAX-VERTEX-NUM] ; /* 顶点向量 */

ArcNode arcs[MAX-VERTEX-NUM][MAX-VERTEX-NUM] ;

/* 邻接矩阵 */

int vexnum, arcnum; /* 图的顶点数和弧数 */

GraphKind kind; /* 图的种类标志 */

} AdjMatrix; /* ( Adjacency Matrix Graph ) */

Page 23: 第 7 章 图

邻接矩阵法的特点如下:

· 存储空间:对于无向图而言, 它的邻接矩阵是对称矩阵

(因为 若 ( vi , vj ) ∈ E ( G ) , 则 ( vj , vi ) ∈ E

( G )),因此我们可以采用特殊矩阵的压缩存储法,即只存储其下三角即可,这样,一个具有 n 个顶点的无向图 G , 它的邻接矩阵需要 n ( n-1 ) /2 个存储空间即可。但对于有向图而

言,其中的弧是有方向的, 即若 <vi , vj> E∈ ( G ),不一

定有 <vj , vi> E∈ ( G ),因此有向图的邻接矩阵不一定是对

称矩阵,对于有向图的邻接矩阵的存储则需要 n2 个存储空间。

Page 24: 第 7 章 图

便于运算:采用邻接矩阵表示法,便于判定图中任意两个顶点之间是否有边相连,即根据 A[ i , j] =0 或 1 来判断。另外还便于求得各个顶点的度。对于无向图而言,其邻接矩阵第 i行元素之和就是图中第 i 个顶点的度:

n

ji jiAvTD

1

],[)(

对于有向图而言,其邻接矩阵第 i行元素之和就是图中第 i 个顶点的出度:

n

ji jiAvOD

1

],[)(

对于有向图而言,其邻接矩阵第 i 列元素之和就是图中第 i 个顶点的入度:

n

ji ijAvID

1

],[)(

Page 25: 第 7 章 图

采用邻接矩阵存储法表示图,很便于实现图的一些基本操作,如实现访问图 G 中 v 顶点第一个邻接点的函数 FirstAdjVertex ( G , v )可按如下步骤实现:

(1) 首先, 由 LocateVertex ( G , v )找到 v 在图中的位置,即 v 在一维数组 vexs 中的序号 i 。

(2) 二维数组 arcs 中第 i行上第一个 adj域非零的分量所在的列号 j ,便是 v 的第一个邻接点在图 G 中的位置。

(3) 取出一维数组 vexs[ j]中的数据信息,即与顶点 v 邻接的第一个邻接点的信息。

Page 26: 第 7 章 图

对于稀疏图而言,不适于用邻接矩阵来存储,因为这样会造成存储空间的浪费。

用邻接矩阵法创建有向网的算法如下:int LocateVertex(AdjMatrix * G, VertexData v) /* 求顶点位置函数 */

{ int j=Error, k;

for(k=0; k<G->vexnum; k++)

if(G->vexs[ k] ==v)

{ j=k; break; }

return(j);

}

Page 27: 第 7 章 图

int CreateDN(AdjMatrix *G) /* 创建一个有向网 */

{ int i, j, k, weight; VertexData v1, v2;

scanf(″%d, %d″, &G->arcnum, &G->vexnum); /*输入图的顶点数和弧数 */

for(i=0; i<G->vexnum; i++) /*初始化邻接矩阵 */

for(j=0; j<G->vexnum; j++)

G->arcs[ i][ j] .adj=INFINITY;

for(i=0; i<G->vexnum; i++)

scanf(″%c″, &G->vexs[ i] ); /* 输入图的顶点 */

for(k=0; k<G->arcnum; k++)

{ scanf(″%c, %c, %d″, &v1, &v2, &weight); /*输入一条弧的两个顶点及权值 */

i=LocateVex-M(G, v1);

j=LocateVex-M(G, v2);

G->arcs[ i][ j] .adj=weight; /* 建立弧 */

}

return(Ok);

} 【算法 7.1 采用邻接矩阵表示法创建有向图】

Page 28: 第 7 章 图

7.2.2 邻接表表示法

图的邻接矩阵表示法 ( 即图的数组表示法 ) ,虽然有其自身的优点,但对于稀疏图来讲,用邻接矩阵的表示方法会造成存储空间的很大浪费。邻接表( Adjacency List )表示法实际上是图的一种链式存储结构。它克服了邻接矩阵的弊病,基本思想是只存有关联的信息,对于图中存在的边信息则存储,而对于不相邻接的顶点则不保留信息。在邻接表中,对图中的每个顶点建立一个带头结点的边链表,如第 i

个边链表中的结点则表示依附于顶点 vi 的边(若是有向图,则表示以vi 为弧尾的弧)。每个边链表的头结点又构成一个表头结点表。这样,一个 n 个顶点的图的邻接表表示由表头结点表与边表两部分构成:

Page 29: 第 7 章 图

(1) 表头结点表:由所有表头结点以顺序结构(向量)的形式存储,以便可以随机访问任一顶点的边链表。 表头结点的结构如图 7.8 ( a )所示。 表头结点由两部分构成,其中数据域( ve

xdata )用于存储顶点的名或其它有关信息;链域( firstarc )用

于指向链表中第一个顶点(即与顶点 vi 邻接的第一个邻接点)。

图 7.8 表头结点和弧结点

vexdata firstarc adjvex info nextarc

( a)表头结点结构 ( b)弧结点结构

Page 30: 第 7 章 图

(2) 边表:由表示图中顶点间邻接关系的 n 个边链表组成,边链表中结点的结构如图 7.8 ( b )所示。它由三部分组成,其中邻接点域 (adjvex) 用于存放与结点 vi 相邻接的顶点在图中的位置;链域( nextarc )用于指向与顶点 vi 相关联的下一条边或弧的结点;数据域( info )用于存放于边或弧相关的信息(如赋权图中的每条边或弧的权值等)。

Page 31: 第 7 章 图

图 7.9 图的邻接表表示法

11

2

3

4

2

4

1

3

2

3

4

(a ) G1µÄÁÚ½Ó±í±íʾ·¨

11

2

3

4

2

3

4

55

4

5

3

5

3

2

3

4

1

2

1

2

±íÍ·½áµã±í ±ß±í

(b ) G2µÄÁÚ½Ó±í±íʾ·¨

±íÍ·½áµã±í ±ß±í

ͼ7.9

Page 32: 第 7 章 图

邻接表存储结构的形式化说明如下:

# define MAX-VERTEX-NUM 10 /* 最多顶点个数 */

typedef enum{DG, DN, UDG, UDN} GraphKind; /* 图的种类 */

typedef struct ArcNode{

int adjvex; /* 该弧指向顶点的位置 */

struct ArcNode *nextarc; /* 指向下一条弧的指针 */

OtherInfo info; /* 与该弧相关的信息 */

} ArcNode;

Page 33: 第 7 章 图

typedef struct VertexNode{

VertexData data; /* 顶点数据 */

ArcNode *firstarc; /* 指向该顶点第一条弧的指针 */

} VertexNode;

typedef struct{

VertexNode vertex[MAX-VERTEX-NUM] ;

int vexnum, arcnum; /* 图的顶点数和弧数 */

GraphKind kind; /* 图的种类标志 */

}AdjList; /* 基于邻接表的图 (Adjacency List Graph)*/

Page 34: 第 7 章 图

■ 存储空间

对于有 n 个顶点, e 条边的无向图而言,若采取邻接表作为存储结构,则需要 n 个表头结点和 2e 个表结点。很显然在边很稀疏(即 e远小于 n(n-1)/2 时)的情况下,用邻接表存储所需的空间比邻接矩阵所需的空间 (n(n-1)/2)要节省得多。

■无向图的度

在无向图的邻接表中,顶点 vi 的度恰好就是第 i 个边链表

上结点的个数。

Page 35: 第 7 章 图

■有向图的度

在有向图中,第 i 个边链表上顶点的个数是顶点 vi 的出度,只需通过表头向量表中找到第 i 个顶点的边链表的头指针,实现顺链查找即可。

如要判定任意两个顶点 (vi 和 vj) 之间是否有边或弧相连,则需要搜索所有的边链表,这样比较麻烦。

求得第 i 个顶点的入度,也必须遍历整个邻接表,在所有边链表中查找邻接点域的值为 i 的结点并计数求和。由此可见, 对于用邻接表方式存储的有向图,求顶点的入度并不方便, 它需要通过扫描整个邻接表才能得到结果。

Page 36: 第 7 章 图

一种解决的方法是逆邻接表法,我们可以对每一顶点 vi

再建立一个逆邻接表,即对每个顶点 vi 建立一个所有以顶点 vi

为弧头的弧的表,如图 7.10 所示。

图 7.10 图 G1 的逆邻接表表示法

11

2

3

4

2

3

4

4

1

1

3

ͼ7.10

Page 37: 第 7 章 图

图 7.11 图的十字链表弧结点、顶点结点结构图

headvex ±íʾ»¡Í·¶¥µã ÔÚͼÖеÄλÖÃ

tlinkÖ¸ÏòÓë´Ë»¡µÄ»¡ βÏàͬµÄÏÂÒ»Ìõ»¡

tailvex headvex hlink tlink

hlinkÖ¸ÏòÓë´Ë»¡µÄ»¡ Í·ÏàͬµÄÏÂÒ»Ìõ»¡

tailvex ±íʾ»¡Î²¶¥µã ÔÚͼÖеÄλÖÃ

(a ) Ê®×ÖÁ´±í»¡½áµã½á¹¹Ê¾Òâ

ͼ7.11

firstinÓò(Á´Óò)ÓÃÓÚÖ¸ÏòÒԸö¥µã×÷Ϊ»¡Í·µÄµÚÒ»¸ö»¡¶¥µã

data firstin firstout

dataÓòÓÃÓÚ´æ´¢Ó붥µãÓйصÄÐÅÏ¢,È綥µãµÄÃû×ÖµÈ

firstoutÓò(Á´Óò)ÓÃÓÚÖ¸ÏòÒԸö¥µã×÷Ϊ»¡Î²µÄµÚÒ»¸ö»¡¶¥µã

(b ) Ê®×ÖÁ´±í¶¥µã½áµã½á¹¹Ê¾Òâ

7.2.3 十字链表

Page 38: 第 7 章 图

图 7.12 图 7.1 中有向图 G1 的十字链表

ͼ7.12

11

2

3

4 4 1

1 2 1 3

3 4

2

3

4

Page 39: 第 7 章 图

图的十字链表结构形式化定义如下: # define MAX-VERTEX-NUM 10 /* 最多顶点个数 */

typedef enum{DG, DN, UDG, UDN} GraphKind; /* 图的种类 */

typedef struct ArcNode {

int tailvex, headvex;

struct ArcNode *hlink, *tlink;

} ArcNode;

typedef struct VertexNode{

VertexData data; /* 顶点信息 */

ArcNode *firstin, *firstout;

}VertexNode;

Page 40: 第 7 章 图

• typedef struct{ • VertexNode vertex[MAX-VERTEX-NUM] ; • int vexnum, arcnum; /* 图的顶点数和弧数 */ • GraphKind kind; /* 图的种类 */• } OrthList; /* 图的十字链表表示法 (Orthogonal List)*/

Page 41: 第 7 章 图

建立一个有向图的十字链表的算法如下: void CrtOrthList ( OrthList g )/* 从终端输入 n 个顶点的信息和 e 条弧的信息, 以建立一个有向图的十字链表 */{ scanf(″%d, %d″, &n, &e); /* 从键盘输入图的顶点个数和弧的个数 */ for (i=0; i<n; i++) {scanf(″%c″, g.vertex[ i] .data ); g.vertex[ i] .firstin=NULL ; g.vertex[ i] .firstout=NULL ; } for (k=0; k<e; k++) {scanf(″%c, %c″, &vt, &vh); i=LocateVertex ( g , vt ); j = LocateVertex ( g , vh ); p=alloc(sizeof(ArcNode)); p->tailvex=i ; p->headvex=j ; p->tlink = g.vertex[ i] .firstout ; g.vertex[ i] .firstout =p ; p->hlink = g.vertex[ j] .firstin ; g.vertex[ j] .firstin =p ; } }/* CrtOrthList */

Page 42: 第 7 章 图

7.3 图 的 遍 历 图的遍历比起树的遍历要复杂得多。由于图中顶点关系是任意的,即图中顶点之间是多对多的关系,图可能是非连通图,图中还可能有回路存在, 因此在访问了某个顶点后,可能沿着某条路径搜索后又回到该顶点上。为了保证图中的各顶点在遍历过程中访问且仅访问一次,需要为每个顶点设一个访问标志,因此我们为图设置一个访问标志数组 visited[ n],用于标示图中每个顶点是否被访问过,它的初始值为 0(“ 假” ) ,表示顶点均未被访问;一旦访问过顶点 vi ,则置访问

标志数组中的 visited[ i]为 1(“真” ) ,以表示该顶点已访问。

Page 43: 第 7 章 图

7.3.1 深度优先搜索

深度优先搜索( Depth-First Search )是指按照深度方向搜索,它类似于树的先根遍历,是树的先根遍历的推广。

深度优先搜索连通子图的基本思想是:

( 1 ) 从图中某个顶点 v0 出发,首先访问 v0 。

( 2 ) 找出刚访问过的顶点 vi 的第一个未被访问的邻接点, 然后访问该顶点。以该顶点为新顶点,重复本步骤,直到当前的顶点没有未被访问的邻接点为止。

( 3 )返回前一个访问过的且仍有未被访问的邻接点的顶点, 找出并访问该顶点的下一个未被访问的邻接点,然后执行步骤 (2) 。

Page 44: 第 7 章 图

采用递归的形式说明,则深度优先搜索连通子图的基本思想可表示为:

( 1 ) 访问出发点 v0 。

( 2 ) 依次以 v0 的未被访问的邻接点为出发点, 深度优先搜索图, 直至图中所有与 v0 有路径相通的顶点都被访问。

若此时图中还有顶点未被访问,则另选图中一个未被访问的顶点作为起始点,重复上述深度优先搜索过程,直至图中所有顶点均被访问过为止。

图 7.15给出了一个深度优先搜索的过程图示,其中实箭头代表访问方向,虚箭头代表回溯方向,箭头旁边的数字代表搜索顺序, A为起始顶点。

Page 45: 第 7 章 图

图 7.15 图的深度优先搜索过程图示

A D G

B E H

C F I

(1) (16)

(2)

(4)

(6)

(15)

(8)

(9)

(7)

(14)

(10)(13)

(12)(11)

(3)(5)

Page 46: 第 7 章 图

首先访问 A ,然后按图中序号对应的顺序进行深度优先搜索。 图中序号对应步骤的解释如下:

( 1 ) 顶点 A 的未访邻接点有 B 、 E 、 D , 首先访问 A 的第一个未访邻接点 B ;

( 2 ) 顶点 B 的未访邻接点有 C 、 E ,首先访问 B 的第一个未访邻接点 C ;

( 3 ) 顶点 C 的未访邻接点只有 F ,访问 F ;

( 4 ) 顶点 F 没有未访邻接点,回溯到 C ;

( 5 ) 顶点 C 已没有未访邻接点,回溯到 B ;

Page 47: 第 7 章 图

( 6 ) 顶点 B 的未访邻接点只剩下 E ,访问 E ;

( 7 ) 顶点 E 的未访邻接点只剩下 G ,访问 G ;

( 8 ) 顶点 G 的未访邻接点有 D 、 H ,首先访问 G 的第一个未访邻接点 D ;

( 9 ) 顶点 D 没有未访邻接点, 回溯到 G ;

( 10 ) 顶点 G 的未访邻接点只剩下 H , 访问 H ;

( 11 ) 顶点 H 的未访邻接点只有 I , 访问 I ;

( 12 ) 顶点 I 没有未访邻接点, 回溯到 H ;

( 13 ) 顶点 H 已没有未访邻接点, 回溯到 G ;

( 14 ) 顶点 G 已没有未访邻接点, 回溯到 E ;

( 15 ) 顶点 E 已没有未访邻接点, 回溯到 B ;

( 16 ) 顶点 B 已没有未访邻接点, 回溯到 A 。

Page 48: 第 7 章 图

# define True 1

# define False 0

# define Error -1 /* 出错 */

# define Ok 1

int visited[MAX-VERTEX-NUM] ; /* 访问标志数组 */

void TraverseGraph (Graph g)

/* 对图 g进行深度优先搜索, Graph 表示图的一种存储结构, 如数组表示法或邻接表等 */

{

for (vi=0; vi<g.vexnum; vi++) visited[ vi] =False ; /* 访问标志数组初始 */

for( vi=0; vi<g.vexnum; vi++) /*调用深度遍历连通子图的操作 */

/* 若图 g 是连通图, 则此循环调用函数只执行一次 */

if (! visited[ vi] ) DepthFirstSearch(g , vi) ; }/* TraverseGraph */

【算法 7.3 深度优先搜索图 g 】

Page 49: 第 7 章 图

void DepthFirstSearch ( Graph g, int v0 ) /*深度遍历 v0 所在的连通子图*/

{ visit ( v0 ); visited[ v0] =True ; /* 访问顶点 v0 , 并置访问标志数组相应分量值 */

w=FirstAdjVertex(g, v0);

while ( w![KG-*2]=-1) /* 邻接点存在 */

{ if(! visited [ w] ) DepthFirstSearch(g, w); /*递归调用 DepthFirst

Search*/

w=NextAdjVertex(g, v0, w); /*找下一个邻接点 */

}

} /*DepthFirstSearch*/ 【算法 7.4 深度遍历 v0 所在的连通子图】

Page 50: 第 7 章 图

1 ) 用邻接矩阵方式实现深度优先搜索

void DepthFirstSearch(AdjMatrix g, int v0)

/* 图 g 为邻接矩阵类型 AdjMatrix */

{visit(v0); visited[ v0] =True;

for ( vj=0; vj<n; vj++)

if (!visited[ vj] && g.arcs[ v0][ vj] .adj==1)

DepthFirstSearch(g, vj);

}/* DepthFirstSearch */

【算法 7.5 采用邻接矩阵的 DepthFirstSearch 】

Page 51: 第 7 章 图

2 ) 用邻接表方式实现深度优先搜索 void DepthFirstSearch(AdjList g, int v0) /* 图 g 为邻接表类型 AdjList */

{visit(v0) ; visited[ v0] =True ;

p=g.vertex[ v0] .firstarc ;

while( p! =NULL )

{if (! visited[ p->adjvex] )

DepthFirstSearch ( g , p->adjvex );

p=p->nextarc ;

}

}/*DepthFirstSearch*/

【算法 7.6 采用邻接表的 DepthFirstSearch 】

Page 52: 第 7 章 图

7.3.2 广度优先搜索 广度优先搜索( Breadth-First Search )是指照广度方向搜索,它类似于树的层次遍历,是树的按层次遍历的推广。广度优先搜索的基本思想是:

( 1 ) 从图中某个顶点 v0 出发,首先访问 v0 。

( 2 ) 依次访问 v0 的各个未被访问的邻接点。

( 3 ) 分别从这些邻接点(端结点)出发,依次访问它们的各个未被访问的邻接点(新的端结点)。访问时应保证:如果 v i 和 vk

为当前端结点, vi 在 vk 之前被访问, 则 vi 的所有未被访问的邻接点应在 vk 的所有未被访问的邻接点之前访问。重复( 3 ), 直到所有端结点均没有未被访问的邻接点为止。

Page 53: 第 7 章 图

图 7.16 图的广度优先搜索过程图示

ͼ7.16

A D G

B E H

C F I

(1)

(4)

(3)

(5) (7)

(8)

(6)

(2)

Page 54: 第 7 章 图

7.4 图的连通性问题7.4.1 无向图的连通分量

图遍历时,对于连通图,无论是广度优先搜索还是深度优先搜索,仅需要调用一次搜索过程,即从任一个顶点出发,便可以遍历图中的各个顶点。对于非连通图,则需要多次调用搜索过程,而每次调用得到的顶点访问序列恰为各连通分量中的顶点集。例如,图 7.17(a) 是一个非连通图,按照它的邻接表进行深度优先搜索遍历,三次调用 Depth

FirstSearch 过程得到的访问顶点序列为:

1 , 2 , 4 , 3 , 9 5 , 6 , 78 , 10

Page 55: 第 7 章 图

图 7.17 图及其连通分量

1

5

6

7

10

8

2

3 4

9

1

2

3

4

5

6

7

8

9

10

2

1

1

2

6

5

5

10

2

8

3

4

4

3

7

4

9

9

(a ) ÎÞÏòͼG5

(b ) G5µÄÁÚ½Ó±í

ͼ7.17

3 4

1 2

9

10

8

5

6

7

(c ) ÎÞÏòͼG5µÄÈý¸öÁ¬Í¨·ÖÁ¿

Page 56: 第 7 章 图

7.4.2 最小生成树

在一个连通网的所有生成树中, 各边的代价之和最小的那

棵生成树称为该连通网的最小代价生成树( Minimum Cost Span

ning Tree ),简称为最小生成树 (MST) 。 最小生成树有如下重

要性质:

设 N=(V, {E}) 是一连通网, U 是顶点集 V 的一个非空子

集。 若( u , v )是一条具有最小权值的边, 其中 u U∈ , v∈

V-U , 则存在一棵包含边( u , v )的最小生成树。

Page 57: 第 7 章 图

我们用反证法来证明这个 MST 性质:

假设不存在这样一棵包含边( u , v )的最小生成树。 任取一棵最小生成树 T ,将( u , v )加入 T 中。根据树的性质,此时 T 中必形成一个包含( u , v )的回路, 且回路中必有一条边( u′, v′ )的权值大于或等于( u , v )的权值。 删除( u′, v′ ),则得到一棵代价小于等于 T 的生成树 T′ ,且 T′ 为一棵包含边( u , v )的最小生成树。 这与假设矛盾, 故该性质得证。

我们可以利用 MST 性质来生成一个连通网的最小生成树。普里姆( Prim )算法和克鲁斯卡尔( Kruskal )算法便是利用了这个性质。

Page 58: 第 7 章 图

1. 普里姆算法

假设 N=(V, {E}) 是连通网, TE 为最小生成树中边的集合。

( 1 ) 初始 U={u0}(u0 V), TE=φ∈ ;

( 2 ) 在所有 u U, v V-U∈ ∈ 的边中选一条代价最小的边( u0 , v

0 )并入集合 TE ,同时将 v0 并入 U ;

( 3 ) 重复( 2 ), 直到 U=V 为止。

此时, TE 中必含有 n-1 条边,则 T= ( V , {TE} )为 N 的最小生成树。

可看出, 普利姆算法逐步增加 U 中的顶点, 可称为“加点法”。

Page 59: 第 7 章 图

为了实现这个算法需要设置一个辅助数组 closedge[ ],以记录从 U

到 V-U 具有最小代价的边。对每个顶点 v V-U∈ ,在辅助数组中存在一个分量 closedge[ v],它包括两个域 vex 和 lowcost ,其中 lowcost 存储该边上的权, 显然有 :

closedge[ v] .lowcost=Min({cost(u, v) | u U})∈

普里姆算法可描述如下:

struct

{ VertexData adjvex;

int lowcost;

} closedge[MAX -VERTEX -NUM ] ;

/* 求最小生成树时的辅助数组 */

Page 60: 第 7 章 图

MiniSpanTree-Prim(AdjMatrix gn, VertexData u)

/* 从顶点 u 出发, 按普里姆算法构造连通网 gn 的最小生成树, 并输出生成树的每条边 */

{ k=LocateVertex(gn, u);

closedge[ k] .lowcost=0; /*初始化, U={u} */

for (i=0; i<gn.vexnum; i++)

if ( i! =k) /* 对 V-U 中的顶点 i , 初始化 closedge[ i] */

{closedge[ i] .adjvex=u; closedge[ i] .lowcost=gn.arcs[ k][ i] .adj; }

for (e=1; e<=gn.vexnum-1; e++) /*找 n-1 条边 (n= gn.vexnum) */

{

k0=Minium(closedge);

/* closedge[ k0]中存有当前最小边( u0, v0 )的信息 */

Page 61: 第 7 章 图

u0= closedge[ k0] .adjvex; /* u0 U*/ ∈

v0= gn.vexs[ k0] /* v0 V-U*/ ∈

printf(u0, v0); /*输出生成树的当前最小边( u0, v0 ) */

closedge[ k0] .lowcost=0; /* 将顶点 v0纳入 U 集合 */

for ( i=0 ; i<vexnum; i++) /* 在顶点 v0 并入 U 之后, 更新 closedge[ i

] */

if ( gn.arcs[ k0][ i] .adj <closedge[ i] .lowcost)

{closedge[ i] .lowcost= gn.arcs[ k0][ i] .adj;

closedge[ i] .adjvex=v0;

}

}

} 【算法 7.9 普里姆算法】

Page 62: 第 7 章 图

图 7.18 普里姆算法构造最小生成树的过程

ͼ7.18

v1

v 2

v 3

v 4

v5

v6

6

1

5

24

6

3 6

5 5

v1

v 2

v 3

v 4

v5

v6

v1

v 2

v 3

v 4

v5

v6

4

1

v 1

v 2

v3

v 4

v 5 v 6

4

1

2

v 1

v 2

v3

v 4

v 5 v 6

4

1

2

5

v 1

v 2

v3

v 4

v 5 v 6

4

1

2

5

3

(a ) Ò»¸öÁ¬Í¨Íø

(d ) ½«v 4 ÄÉÈëUÖÐ (e ) ½«v 2 ÄÉÈëUÖÐ (f) ½«v 5 ÄÉÈëUÖÐ

(c ) ½«v 6 ÄÉÈëUÖÐ(b ) ½«v 3 ÄÉÈëUÖÐ

Page 63: 第 7 章 图

表 7 - 1 普里姆算法各参量的变化

Page 64: 第 7 章 图

2. 克鲁斯卡尔算法

假设 N=(V, {E}) 是连通网,将 N 中的边按权值从小到大的顺序排列 : (1) 将 n 个顶点看成 n 个集合;

(2) 按权值由小到大的顺序选择边, 所选边应满足两个顶点不在同一个顶点集合内,将该边放到生成树边的集合中。同时将该边的两个顶点所在的顶点集合合并;

(3) 重复 (2), 直到所有的顶点都在同一个顶点集合内。

可以看出,克鲁斯卡尔算法逐步增加生成树的边, 可称为“加边法”。

Page 65: 第 7 章 图

例如,对于图 7.18 所示的连通网, 将所有的边按权值从小到大的顺序排列为:

权值 1 2 3 4 5 5 5 6 6 6

边 (v1,v3) (v4,v6) (v2,v5) (v3,v6) (v1,v4) (v2,v3) (v3,v4) (v1,v2)

(v3,v5) (v5,v6)

(v1,v3) (v4,v6) (v2,v5) (v3,v6) (v2,v3)

经过筛选所得到边的顺序为:

在选择第五条边时,因为 v1 、 v4 已经在同一集合内,如果选( v1 , v4 ), 则会形成回路, 所以选( v#-2 , v#-3 )。

Page 66: 第 7 章 图

图 7.19 克鲁斯卡尔算法执行示意

ͼ7.19

1 2

5 4

36

5

6

618

1433

2119

16

11

(a ) Ò»¸öÁ¬Í¨Íø

(e ) ¼ÓÈë±ß(2£¬6)

1 2

5 4

36

511

(f ) ¼ÓÈë±ß(1£¬2)

1 2

5 4

36

511

(b ) ×îСÉú³ÉÊ÷µÄ³õʼ״̬

1 2

5 4

36

(c ) ¼ÓÈë±ß(2£¬3)

1 2

5 4

36

(d ) ¼ÓÈë±ß(2£¬4)

1 2

5 4

36

5 5

6

6 6

16

(g ) ¼ÓÈë±ß(5£¬4)

1 2

5 4

36

511

6

16

18

Page 67: 第 7 章 图

设 N= (V, {E}), 最小生成树的初态为 T= ( V, { } )。

( 1 ) 待选的边: (2, 3)->5, (2, 4)->6, (3, 4)->6, (2, 6)->

11, (4, 6)->14, (1, 2)->16, (4, 5)->18, (1, 5)->19, (1, 6)->21,

(5, 6)->33 。

顶点集合状态: {1} , {2} , {3} , {4} , {5} , {6} 。

最小生成树的边的集合: { } 。

Page 68: 第 7 章 图

( 2) 从待选边中选一条权值最小的边为 : (2, 3)->5 。

待选的边变为: (2, 4)->6, (3, 4)->6, (2, 6)->11, (4, 6)->14,

(1, 2)->16, (4, 5)->18, (1, 5)->19, (1, 6)->21, (5, 6)->33 。

顶点集合状态变为: {1} , {2 , 3} , {4} , {5} , {6} 。

最小生成树的边的集合: {(2, 3)} 。

Page 69: 第 7 章 图

( 3 ) 从待选边中选一条权值最小的边为 : (2, 4)->6 。

待选的边变为: (3, 4)->6, (2, 6)->11, (4, 6)->14, (1, 2)->16,

(4, 5)->18 , (1, 5)->19, (1, 6)->21, (5, 6)->33 。

顶点集合状态变为: {1} , {2 , 3 , 4} , {5} , {6} 。

最小生成树的边的集合: {(2, 3), (2, 4)} 。

Page 70: 第 7 章 图

( 4 ) 从待选边中选一条权值最小的边为 : (3, 4)->6 , 由于 3 、

4 在同一个顶点集合 {2 , 3 , 4}内,故放弃。重新从待选边中

选一条权值最小的边为 : (2, 6)->11 。

待选的边变为: (4, 6)->14 , (1, 2)->16 , (4, 5)->18 , (1, 5)->

19, (1, 6)->21 , (5, 6)->33 。

顶点集合状态变为: {1} , {2 , 3 , 4 , 6} , {5} 。

最小生成树的边的集合: {(2, 3) , (2, 4), (2, 6)} 。

Page 71: 第 7 章 图

( 5 ) 从待选边中选一条权值最小的边为 : (4, 6)->14 , 由于

4 、 6 在同一个顶点集合 {2 , 3 , 4 , 6}内,故放弃。重新从

待选边中选一条权值最小的边为 : (1, 2)->16 。

待选的边变为: (4, 5)->18 , (1, 5)->19, (1, 6)->21, (5, 6)->3

3 。 顶点集合状态变为: {1 , 2 , 3 , 4 , 6} , {5} 。

最小生成树的边的集合: {(2, 3) , (2, 4), (2, 6) , (1, 2)} 。

Page 72: 第 7 章 图

( 6 ) 从待选边中选一条权值最小的边为 : (4, 5)->18 。

待选的边变为: (1, 5)->19, (1, 6)->21 , (5, 6)->33 。

顶点集合状态变为: {1 , 2 , 3 , 4 , 6 , 5} 。

最小生成树的边的集合: {(2, 3) , (2, 4), (2,

6) , (1, 2), (4 , 5)} 。

至此, 所有的顶点都在同一个顶点集合 {1 , 2 , 3 , 4 , 6 , 5}里,算法结束。所得最小生成树如图 7.20 所示,其代价为: 5+6+11+16+18=56 。

Page 73: 第 7 章 图

图 7.20 最小生成树

1 2

5 4

36

5

116

18

ͼ7.20

16

Page 74: 第 7 章 图

7.5 有向无环图的应用

用顶点表示活动,用弧表示活动间的优先关系的有向无环图,称为顶点表示活动的网( Activity On Vertex Network ),简称为 AOV- 网。 表 7 – 2 课程关系

Page 75: 第 7 章 图

图 7.21 表示课程之间优先关系的有向无环图

C2

C 1 C 8

C9

C3

C 4

C5

C 6

C7

Page 76: 第 7 章 图

在有向图 G= ( V , {E} )中, V 中顶点的线性序列( vi1, vi2, vi3 … , vin )称为拓扑序列,序列必须满足如下条件: 对序列中任意两个顶点 vi 、 vj ,在 G 中有一条从 vi 到 vj

的路径,则在序列中 vi 必排在 vj 之前。

例如,图 7.21 的一个拓扑序列为: C1, C2, C3, C4, C5, C8, C

9, C7, C6 。

Page 77: 第 7 章 图

AOV- 网的特性如下:

· 若 vi 为 vj 的先行活动, vj 为 vk 的先行活动,则 vi 必为 vk 的先行活动,即先行关系具有可传递性。从离散数学的观点来看,若有 <v

i, vj> 、 <vj, vk> ,则必存在 <vi, vk> 。

显然,在 AOV- 网中不能存在回路,否则回路中的活动就会互为前驱,从而无法执行。

· AOV- 网的拓扑序列不是唯一的。

例如,图 7.21 的另一个拓扑序列为: C1, C2, C3, C8, C4, C5, C9, C7,

C6 。

Page 78: 第 7 章 图

拓扑排序的基本思想为:

( 1 ) 从有向图中选一个无前驱的顶点输出;

( 2 ) 将此顶点和以它为起点的弧删除;

( 3 ) 重复( 1 )、( 2 ),直到不存在无前驱的顶点;

( 4 ) 若此时输出的顶点数小于有向图中的顶点数,则说明有向图中存在回路,否则输出的顶点的顺序即为一个拓扑序列。

Page 79: 第 7 章 图

图 7.22 AOV- 网

1 2

4 3

56

ͼ7.22

例如,对于图 7.22 所示的 AOV- 网,执行上述过程可以得到如下拓扑序列: v1 , v6 , v4 , v3 , v2 , v5 或 v1 , v3 , v2 , v6 , v4 , v5

Page 80: 第 7 章 图

7.6 最 短 路 径 7.6.1 最短路径的概念

在一个图中,从一个顶点到另一个顶点如果存在路径,则成该路径长度为路径上边的数目,如果两顶点间存在多条路径,则最短路径自然是指路径最短的一条。对于一个网来说,路径长度是指路径上个边的权值之和,相应地,最短路径也就是指路径上个边权值之和最小的一条路径。

Page 81: 第 7 章 图

7.6.2 求单源最短路径的方法

怎样求一个源点到其余各个顶点的最短路径呢?可以使用迪杰斯特拉算法。该算法的基本思想是:把图中所有顶点分成两组,第一组包括已确定最短路径的顶点,第二组包括尚未确定最短路径的顶点,按最短路径长度递增的顺序逐个把第二组的顶点加入到第一组中去,直到从源点出发的可以到达的所有顶点都已包括在第一组中。在此过程中,总保持从源点到第一组个顶点的最短路径长度都不大于从源点到第二组的任何顶点的最短路径长度。

Page 82: 第 7 章 图

图 7.26 一个带权有向图及其邻接矩阵

v 0

v2

v5

v 4v 1

v3

20

15 3

10

50 10

45

15 35 3020 ͼ7.26

0 1 2 3 4

5

0 1 2 3 4 5

0 50 10 ∞ 45 ∞

∞ 0 15 ∞ 10 ∞

20 ∞ 0 15 ∞ ∞

∞ 20 ∞ 0 35 ∞

∞ ∞ ∞ 30 0 ∞

∞ ∞ ∞ 3 ∞ 0

Page 83: 第 7 章 图

表 7 – 3 v0 到其它顶点的最短路径

Page 84: 第 7 章 图

当我们按长度递增的顺序来产生各个最短路径时,设 S 为已经求得的最短路径的终点集合。

我们可以证明:下一条最短路径或者是弧( v0 , vx ),或者是中

间经过 S 中的某些顶点而后到达 vx 的路径。

可用反证法:假设下一条最短路径上有一个顶点 vy 不在 S 中, 即

此路径为( v0 ,…, vy ,…, vx )。显然,( v0 ,…, vy )的长度

小于( v0 ,…, vy ,…, vx )的长度,故下一条最短路径应为( v0 ,

…, vy ),这与假设的下一条最短路径( v0 ,…, vy ,…, vx )相

矛盾!因此,下一条最短路径上不可能有不在 S 中的顶点 vy ,即假设

不成立。

Page 85: 第 7 章 图

一般情况下,下一条长度最短的最短路径的长度必是 dist[ j] =Min{dist[ i] | vi V∈ - S}

其中, dist[ i]或者是弧( v0 , vi )上的权值, 或者是 dist[ k

]( vk S∈ )和弧( vk , vi )上的权值之和。我们可以将图中的顶点分为两组:

S—— 已求出的最短路径的终点集合 (开始为 {v0}) 。

V-S——尚未求出最短路径的顶点集合 (开始为 V- {v0} 的全部结点 ) 。 按最短路径长度的递增顺序逐个将第二组的顶点加入到第

一组中。

Page 86: 第 7 章 图

迪杰斯特拉算法的主要步骤如下: (1) g 为用邻接矩阵表示的带权图。

S←{v0} , dist[ i] = g.arcs[ v0][ vi];

将 v0 到其余顶点的路径长度初始化为权值;

(2) 选择 vk ,使得:

vk 为目前求得的下一条从 v0 出发的最短路径的终点。

(3) 修改从 v0 出发到集合 V…S 上任一顶点 vi 的最短路径的长度。如果:

dist[ k] + g.arcs[ k][ i] <dist[ i]

则将 dist[ i]修改为: dist[ k] + g.arcs[ k][ i]

(4) 重复 (2) 、 (3) n-1 次,即可按最短路径长度的递增顺序,逐个求出 v0

到图中其它每个顶点的最短路径。

dist[ vk] =min(dist[ i] | vi V-S) ∈

Page 87: 第 7 章 图