第7章 图

100
第7第 7.1 图图图图图图图 7.2 图图图图图图 7.3 图图图图 7.4 图图图图图 7.5 图图图图图图图图 7.6 图图图图 图图

Upload: cleantha-thanos

Post on 30-Dec-2015

42 views

Category:

Documents


8 download

DESCRIPTION

第7章 图. 7.1 图的定义和术语 7.2 图的存储结构 7.3 图的遍历 7.4 最小生成树 7.5 有向无环图及应用 7.6 最短路径 习题. 7.1 图的定义和术语. 一、图的定义 图 (Graph) :是一种多对多的结构关系,每个元素可以有零个或多个直接前趋;零个或多个直接后继; 图 G 由两个集合构成,记作: G=(V,E) 其中: V: 顶点的非空有限集合, E: 边的有限集合,边是顶点的无序对或有序对集合。 网 :带权的图。 无向图 :在图 G 中,若所有边是无向边。 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第7章  图

第 7 章 图7.1 图的定义和术语

7.2 图的存储结构

7.3 图的遍历

7.4 最小生成树

7.5 有向无环图及应用

7.6 最短路径

习题

Page 2: 第7章  图

7.1 图的定义和术语

一、图的定义• 图 (Graph) :是一种多对多的结构关系,每个元素可以有零个或多个直接前趋;零个或多个直接后继;

• 图 G 由两个集合构成,记作:G=(V,E)

其中:– V :顶点的非空有限集合,– E :边的有限集合,边是顶点的无序对或有序对集合。

• 网:带权的图。• 无向图:在图 G 中,若所有边是无向边。• 有向图:在图 G 中,若所有边是有向边。• 混合图:在图 G 中,既有无向边也有有向边。

Page 3: 第7章  图

例 1 :G1=(V1,E1)

V1={v1,v2,v3,v4 ,v5 }

E1={(v1,v2),(v1,v4),(v2,v3),(v2,v5),(v3,v4),(v3,v5)}

G1 图示

顶点边

V5

V1

V2

V4

V3

Page 4: 第7章  图

G2 图示

顶点 弧 弧尾 ( 初始点 ) 弧头 ( 终端点 )

V1 V2

V3 V4

例 2 :G2=(V2,E2)

V2={v1,v2,v3,v4 }

E2={<v1,v2>, <v1,v3>, <v3,v4> , <v4,v1>}

Page 5: 第7章  图

抽象数据类型图的定义ADT Graph {

• 数据对象 V : V 是具有相同特性的数据元素的集合,称为顶点集。

• 数据关系 R : R= {VR}

VR= {<v,w>| v,w V∈ 且 P(v,w), <v,w> 表示从 v到 w的弧,谓词 P(v,w) 定义了弧 <v,w> 的意义或信息 }

• 基本操作 P:

……

} ADT Graph

Page 6: 第7章  图

基本操作 结构的建立和销毁 :

• CreateGraph(&G,V,VR);

按 V和 VR 的定义构造图 G 。• DestroyGraph(&G);

销毁图 G 。

对顶点的访问操作 :

• LocateVex(G, u);

若 G 中存在顶点 u ,则返回该顶点在图中位置;否则返回其它信息。

• GetVex(G, v);

返回 v 的值。• PutVex(&G, v, value);

对 v 赋值 value 。

Page 7: 第7章  图

对邻接点的操作 :• FirstAdjVex(G, v);

返回 v 的第一个邻接点。若该顶点在 G 中没有邻接点,则返回“空”。

• NextAdjVex(G, v, w); 返回 v 的(相对于 w 的)下一个邻接点。若 w是 v 的最后一个邻接点,则返回“空”

插入或删除顶点• InsertVex(&G, v);

在图 G 中增添新顶点 v 。• DeleteVex(&G, v);

删除 G 中顶点 v 及其相关的弧。

Page 8: 第7章  图

插入和删除弧• InsertArc(&G, v, w);

在 G 中增添弧 <v,w> ,若 G 是无向的,则还增添对称弧 <w,v> 。• DeleteArc(&G, v, w);

在 G 中删除弧 <v,w> ,若 G 是无向的,则还删除对称弧 <w,v> 。

遍历• DFSTraverse(G, v, Visit());

从顶点 v 起深度优先遍历图 G ,并对每个顶点调用函数 Visit 一次且仅一次。

• BFSTraverse(G, v, Visit());

从顶点 v 起广度优先遍历图 G ,并对每个顶点调用函数 Visit 一次且仅一次。

Page 9: 第7章  图

二、图的应用举例• 例 1. 交通图(公路、铁路)

顶点:地点边:连接地点的公路交通图中的有单行道双行道,分别用有向边、无向边表示;

• 例 2. 电路图顶点:元件边:连接元件之间的线路

V5

V1

V2

V4

V3

Page 10: 第7章  图

• 例 3. 通讯线路图顶点:地点边:地点间的连线

• 例 4. 各种流程图(如:产品的生产流程图)顶点:工序边:各道工序之间的顺序关系

Page 11: 第7章  图

三、图的基本术语• 完全图:图 G 任意两个顶点都有一条边相连接

若 n 个顶点的无向图有 n(n-1)/2 条边 , 称为无向完全图

若 n 个顶点的有向图有 n(n-1) 条边 , 称为有向完全图

• 稀疏图:边较少的图。通常 e<<nlogn

• 稠密图:边很多的图。e1

V5

V1

V2

V4

V3

Page 12: 第7章  图

• 邻接 (adjacent) 点:边的两个顶点• 关联 (incident) 边:若边 e= (v, u), 则称顶点 v、 u 关联边 e

• 顶点的度:和某顶点相连的邻接点数• 顶点的入度:指向某顶点的弧的数目• 顶点的出度:从某顶点出发的弧的数目

设图 G 的顶点数为 n ,边数为 e

• 图的所有顶点的度数和 = 2*e每条边对图的所有顶点的度数和“贡献” 2 度

V1

V2

V3

V4

V5

V1 V2

V4

V3

Page 13: 第7章  图

• 路径:两个顶点之间的顶点序列。该序列的每个顶点与其前驱是邻接点,每个顶点与其后继也是邻接点

• 回路(环):第一顶点和最后顶点相同的路径。图 1: V1,V2,V3,V4 是 V1到 V4 的路径; V1,V2,V3,V4,V1 是回路;

图 2: V1,V3,V4 是 V1到 V4 的路径; V1,V3,V4,V1 是回路。

• 简单路径: 顶点不重复的路径。

V5

V1

V2

V4

V3

V1

V2

V3

V4图 1 图 2

Page 14: 第7章  图

V5

V6

V3

V1

V2

V4

连通图 非连通图

V5

V1

V2

V4

V3

• 连通: 两个顶点之间有路径• 连通图: 任意两个顶点之间有路径 ( 无向图 )

• 强连通图:任意两个顶点之间有双向路径 ( 有向图 )

Page 15: 第7章  图

V5

V1

V2

V4

V3

V5

V1

V2

V3

V5

V1

V2

V4

V3

图 1 图 2 图 3

• 子图:设有两个图G=( V, E)、 G1=( V1, E1),若 V1 V, E1 E ,则称 G1是 G 的子图;例:图 2 、图 3 是 图 1 的子图

Page 16: 第7章  图

V5

V6

V3

V1

V2

V4

连通分图

• 连通分图: 无向图中的极大连通子图。• 强连通分量:有向图中的极大强连通子图。• 极大(强)连通子图:该子图是图 G 的 ( 强 ) 连通子图,将 G 的任何不在该子图中的顶点加入,子图不再(强)连通。

Page 17: 第7章  图

• 生成树:包含无向图 G 所有顶点的的极小连通子图。• 极小连通子图:该子图是 G 的连通子图,在该子图中删除任何一条边,子图不再连通

若 T是 G 的生成树当且仅当 T 满足如下条件:• T是 G 的连通子图• T 包含 G 的所有顶点• T 中无回路

V5

V1

V2

V4

V3

V5

V1

V2

V4

V3

Page 18: 第7章  图

7.2 图的存储结构图的存储结构至少要保存两类信息:

• 1 )顶点的数据 • 2 )顶点间的关系

顶点的编号• 为了使图的存储结构与图一一对应,在讨论图的存储结构时,首先要给图的所有顶点编号。设 G=<V, E> 是图 , V={v1,v2,v3, … vn } ,设顶点的的下标为它的编号。

本课程介绍两类图的存储结构• 数组表示法• 邻接表(邻接表,逆邻接表)

Page 19: 第7章  图

A[i][j]=1 若 (vi,vi+1)E 或 <vi,vi+1>E

0 否则

V5

V1

V2

V4

V3

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

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

V1

V2

V3

V4

一、数组表示法• 用邻接矩阵表示顶点间的关系。• 邻接矩阵: G 的邻接矩阵是满足如下条件的 n 阶矩阵:

Page 20: 第7章  图

网(即有权图)的邻接矩阵

A [ i ][ j ]=Wij <vi, vj> 或( vi, vj )∈ VR∞ 无边(弧)

v1 v2

v3

v4

N

v5

v6

5

48

97

5

5

61

3

∞ 5 ∞ 7 ∞ ∞∞ ∞ 4 ∞ ∞ ∞ 8 ∞ ∞ ∞ ∞ 9∞ ∞ 5 ∞ ∞ 6∞ ∞ ∞ 5 ∞ ∞ 3 ∞ ∞ ∞ 1 ∞

Page 21: 第7章  图

012345

m-1

V1V2V3V4V5

01010

10101

01234

mm+1m+2m+3m+4

顶点的存储:用一维数组存储(按编号顺序);顶点间关系:用二维数组存储图的邻接矩阵。

存储顶点的一维数

组 存储邻接矩阵的二维数

Page 22: 第7章  图

#define MAX_VERTEX_NUM m // 最大顶点个数typedef enum {DG,DN,UDG,UDN} GraphKind;

//{ 有向图,有向网,无向图,无向网 }typedef struct ArcCell { VRType adj; //VRType 是顶点关系类型。 // 无权图:用 1 或 0 表示相邻否;对带权图:则为权值类型}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];typedef struct { VertexType vexs[MAX_VERTEX_NUM]; // 存储顶点 AdjMatrix arcs; // 存储邻接矩阵 int vexnum, arcnum; // 图的当前顶点数和弧数 GraphKind kind; // 图的种类标志}Mgraph;

数组表示法类型定义

Page 23: 第7章  图

G.vexs

G.arcs

G.vexnumG.arcnu G.kind

V1

0

n

e

UDG

顶点数组

存储邻接矩阵的二维数组

• 设 G是Mgraph 类型的变量,用于存储无向图,该图有 n 个顶点, e 条边。G 的图示如下:

Page 24: 第7章  图

无向图数组表示法特点:• 1 )无向图邻接矩阵是对称矩阵,同一条边表示了两次;• 2 )顶点 v 的度:二维数组对应行(或列)中 1 的个数;• 3 )两顶点是否为邻接点:只需判二维数组对应分量是否为

1 ;• 4 )增加、删除边:只需对二维数组对应分量赋值 1 或清 0 ;• 5 )设存储顶点的一维数组大小为 n, G占用存储空间:

n+n2。 G占用存储空间只与它的顶点数有关,与边数无关。对有向图的数组表示法可做类似的讨论。

Page 25: 第7章  图

用邻接矩阵生成无向网的算法

Status CreateUDN(Mgraph &G) {

//无向网的构造,用邻接矩阵表示

scanf(&G.vexnum, &G.arcnum, &IncInfo);

for(i=0;i<G.vexnum,;++i) scanf(&G.vexs[i] ); //构造顶点向量

for(i=0; i<G.vexnum; ++i) //初始化邻接矩阵 for(j=0;j<G.vexnum;++j) G.arcs[i][j]={INFINITY, NULL};

for(k=0;k<G.arcnum;++k){ //构造邻接矩阵 scanf(&v1, &v2, &w); //输入弧的两顶点以及对应权值 i=LocateVex(G,v1); j=LocateVex(G,v2); //找到顶点在矩阵中的位置 G.arcs[i][j].adj=w; //输入对应权值 if(IncInfo) Input(*G.arcs[i][j].info); //如果弧有信息则填入 G.arcs[i][j] = G.arcs [j] [i] ; //无向网是对称矩阵 }

return OK;

} //CreateUDN

对于 n 个顶点 e 条弧的网,时间复杂度 = O(n2+n+e*n)

Page 26: 第7章  图

二、邻接表1. 无向图的邻接表• 顶点:按编号顺序将顶点数据存储在一维数组中;• 关联同一顶点的边:用线性链表存储。

V5

V1

V2

V4

V3

0 1 2 3 4

m-1

V1V2V3V4V5

1 3

422

1

10

0 23

4

Page 27: 第7章  图

#define MAX_VERTEX_NUM 20typedef struct ArcNode{ // 弧的类型定义 int adjvex; // 弧指向的顶点的位置 struct ArcNode *nextarc; // 指向下一条弧的指针 InfoType *info;}ArcNode;

图的邻接表类型定义

typedef struct Vnode { // 顶点的类型定义 VertexType data; // 顶点信息 ArcNode * firstarc; // 指向关联该顶点的弧链表}Vnode, AjList[MAX_VERTEX_NUM];

typedef struct { // 图的类型定义 AdjList vertices; int vexnum, arcnum; // 图的顶点数和弧数 int kind; // 图的种类标志}ALGraph;

Page 28: 第7章  图

V5

V1

V2

V4

V3

无向图 G1

G.vertices

G.vexnum

G.arcnu

G.kind

0 1 2 4

m-1

V1

V2

V3

V4

V5

5

6

UDG

3 1

10

1

4

2

2

4 2

3

data firstarc adjvex nextarc

• 设 G是 ALGraph 类型的变量,用于存储无向图G1 ,该图有 n 个顶点, e 条边G 的图示如下:

0

Page 29: 第7章  图

无向图的邻接表的特点• 1 )在 G 邻接表中,同一条边对应两个结点;• 2 )顶点 v 的度:等于 v 对应线性链表的长度;• 3 )两顶点是否邻接:要看 v 对应线性链表中有无对应的结点 u;

• 4 )增减边:要在两个单链表插入、删除结点;• 5 )设存储顶点的一维数组大小为 n, 图的边数为

e, G占用存储空间为: n+2*e。 G占用存储空间与 G 的顶点数、边数均有关。

Page 30: 第7章  图

2. 有向图的邻接表和逆邻接表• 1 )有向图的邻接表

顶点:用一维数组存储(按编号顺序)以同一顶点为起点的弧:用线性链表存储

D.vertices

D.vexnum D.arcnu

D.kind

V1V2V3V4

4

4

DG

2 1

30

V1

V2

V3

V4

0 1 2 3

m-1

顶点表 出边表

Page 31: 第7章  图

• 2 )有向图的逆邻接表顶点:用一维数组存储(按编号顺序)以同一顶点为终点的弧:用线性链表存储表

D.vertices

D.vexnum D.arcnu

D.kind

V1V2V3V4

4

4

DG

002

3

V1

V2

V3

V4

0 1 2 3

m-1

顶点表 入边表

Page 32: 第7章  图

网络 ( 带权图 ) 的邻接表

D.vertices

D.vexnum D.arcnu

D.kind

ABCD

4

4

DG

1 5 0 1 2 3

m-1

BB

AA

CC

DD6

95 2

8

3 62 83 21 9

顶点表 出边表

Page 33: 第7章  图

比较:邻接矩阵和邻接表

邻接矩阵 邻接表联系 邻接表中每个链表对应于邻接矩阵中的一行

链表中结点个数等于一行中非零元素的个数。区别 对于任一确定的无向图,

邻接矩阵是唯一的邻接表不唯一

图的操作

容易:求某顶点的度、判断顶点之间是否有边、找顶点的邻接点

容易:寻找顶点的邻接点麻烦:判断两顶点间是否有边,需搜索两结点对应的单链表

空间复杂度

O(n2) O(n+2e)或 O(n+e)

用途 稠密图的存储 稀疏图的存储e<<n(n-1)/2

Page 34: 第7章  图

三、十字链表(自学)它是有向图的另一种链式结构。 思路:邻接表、逆邻接表的结合。

• 1 、每条弧对应一个结点 ( 称为弧结点)• 2 、每个顶点也对应一个结点(称为顶点结点)

Page 35: 第7章  图

tailvex headvex hlink tlink info

顶点结点

弧结点

tailvex: 弧尾顶点位置 headvex: 弧头顶点位置hlink: 弧头相同的下一弧位置tlink: 弧尾相同的下一弧位置info: 弧信息

data : 存储顶点信息。Firstin : 以顶点为弧头的第一条弧结点。Firstout: 以顶点为弧尾的第一条弧结点。

data Firstin Firstout

Page 36: 第7章  图

十字链表存储结构:

typedef struct ArcBox { // 弧结点结构 int tailvex , headvex ; struct ArcBox * hlink , *tlink; InfoType *info;} ArcBox ;

Typedef struct VexNode{ // 顶点结构 VertexType data; ArcBox * firstin,*firstout;} VexNode;

#define MAX_VERTEX_NUM 20Typedef struct { VexNode xlist[MAX_VERTEX_NUM ]; // 表头向量 int vexnum, arcnum;} OLGraph;

Page 37: 第7章  图

0 v1

1 v2

2 v3

3 v4

v1 v2

v3 v4

0 1 0 ^2

2 0 2 ^^3

例:画出有向图的十字链表。

十字链表优点:容易操作,如求顶点的入度、出度等。 空间复杂度与邻接表相同;建立的时间复杂度与邻接表相同。

3 ^0 3 ^^1 3 ^^2

^

数组下标

tailvex

headvex hlink

tlink

firstin firstout

Page 38: 第7章  图

四、邻接多重表(自学)无向图的另一种存储结构需要对边操作时,无向图常采用此种结构存储。

• 1. 每条边只对应一个结点(边结点 )比较:邻接表中同一条边用两个结点表示

• 2. 每个顶点也对应一个结点(顶点结点 )

Page 39: 第7章  图

mark ivex ilink jvex jlink info

边结点

mark :标志域,如处理过或搜索过。ivex , jvex : 顶点域,边依附的两个顶点位置。 ilink: 指向下一条依附顶点 i 的边结点位置。jlink; 指向下一条依附顶点 j 的边结点位置。info: 边信息,如权值等。

data : 存储顶点信息。Firstedge : 依附顶点的第一条边结点。

data Firstedge

顶点结点

Page 40: 第7章  图

40

无向图的邻接多重表存储表示

typedef struct Ebox {

VisitIf mark; // 访问标记 int ivex, jvex; // 该边依附的两个顶点的位置 struct EBox *ilink, *jlink;

InfoType *info; // 该边信息指针} Ebox;

typedef struct Ebox {

VisitIf mark; // 访问标记 int ivex, jvex; // 该边依附的两个顶点的位置 struct EBox *ilink, *jlink;

InfoType *info; // 该边信息指针} Ebox;

typedef struct VexBox { VertexType data; EBox *firstedge; // 指向第一条依附该顶点的边} VexBox;

typedef struct VexBox { VertexType data; EBox *firstedge; // 指向第一条依附该顶点的边} VexBox;

typedef struct { // 邻接多重表 VexBox adjmulist[MAX_VERTEX_NUM]; int vexnum, edgenum; } AMLGraph;

typedef struct { // 邻接多重表 VexBox adjmulist[MAX_VERTEX_NUM]; int vexnum, edgenum; } AMLGraph;

Page 41: 第7章  图

41

0 v1

1 v2

2 v3

3 v4

4 v5

0 1 ^…

0 3

1 2 ^1 4

2 3 ^2 4

^3 ^4

v1 v2

v3

v5v4v4

例:画出无向图的邻接多重表

邻接多重表优点:容易操作,如求顶点的度等。空间复杂度与邻接表相同;建立的时间复杂度与邻接表相同。

数组下标不属结点分量

ivex jvex

ilink jlink

firstedge

Page 42: 第7章  图

7.3 图的遍历图的遍历:从图的某顶点出发,访问图中所有顶点,并且每个顶点仅访问一次。• 图中可能有回路,遍历可能沿回路又回到已遍历过的结点。为避免同一顶点被多次访问,必须为每个被访问的顶点作一标志。

• 为此引入一辅助数组, 记录每个顶点是否被访问过。

有两种遍历方法:• 深度优先遍历 ( DFS, Depth First Search )

• 广度优先遍历 ( BFS , Breadth First Search )

Page 43: 第7章  图

V8

V7V6V5V4

V3

V2

V1

注:为简单起见,只讨论非空连通图的遍历

7.3.1 深度优先遍历(类似于树的先根遍历)从图中某顶点 v 出发: • 1 )访问顶点 v;

• 2 )从 v 的未被访问的邻接点出发,继续对图进行深度优先遍历;

例:图 G 中,以 V1 为起点的深度优先序列:• V1, V2, V4, V8, V5, V3, V6, V7

0 1 2 3 4 5 6 7 visited 1 1 0 1 1 0 0 1

Page 44: 第7章  图

深度优先遍历算法void DFS(Graph G, int v) {

// 从第 v 个顶点出发,递归地深度优先遍历连通图 G 。// v 是顶点在一维数组中的位置,假设 G 是非空图 visited[v] =TRUE;

Visit(v); // 访问第 v 个顶点 for (w= FirstAdjVex(G, v); w>=0; w=NextAdjVex(G, v, w))

if (!visited[w]) DFS(G, w);

//对 v 的尚未访问的邻接顶点 w递归调用 DFS

}

Boolean visited[MAX_VERTEX_NUM] // 访问标志数组 , visited[v]=TRUE 表示顶点 v 已被访问 0

1234

m-1

00000

visited

Page 45: 第7章  图

DFS 算法效率分析设图中有 n 个顶点, e 条边• 遍历图的过程实质上是对每个顶点查其找邻接点• 邻接矩阵:查找每个顶点的邻接点所需的时间为

O(n2) 。• 邻接表:查找邻接点需扫描 e 个结点,加上访问

n 个头结点的时间,因此遍历图的时间复杂度为O(n+e) 。

结论:• 稠密图适于在邻接矩阵上进行深度遍历;• 稀疏图适于在邻接表上进行深度遍历。

Page 46: 第7章  图

V8

V7V6V5V4

V3

V2

V1

7.3.2 广度优先遍历(类似于树的按层遍历)从图中某顶点 v 出发:• 1 )访问顶点 v ;

• 2 )访问 v 的所有未被访问的邻接点 w1 ,w2 , …wk ;

• 3 )依次从这些邻接点出发,访问它们的所有未被访问的邻接点 ; 依此类推,直到图中所有访问过的顶点的邻接点都被访问;

例:图 G 中,以 V1 起点的的广度优先序列:• V1, V2, V3, V4, V5, V6, V7, V8

0 1 2 3 4 5 6 7 visited 1 0 0 0 0 0 0 0

Page 47: 第7章  图

为实现 3 )• 需要保存在步骤 2 )中访问的顶点;• 访问这些顶点邻接点的顺序为:

先保存的顶点,其邻接点先被访问。

需设置一队列 Q ,保存已访问的顶点,并控制遍历顶点的顺序。

Page 48: 第7章  图

void BFSTraverse(Graph G,int v,Status (* Visit)(int v)) {//从 v 出发,广度优先非递归遍历连通图 G 。// 使用辅助队列 Q 和访问标志数组 visited 。 for (u=0; u< G.vexnum; ++u) visited[u]=FALSE; InitQueue(Q); // 建空的辅助队列 Q visited[v]=TRUE; Visit(v); EnQueue(Q,v) // 访问 v,v 入队 while(!QueueEmpty(Q)){ DeQueue(Q,u); //队头元素出队 , 并赋值给 u // 访问 u 所有未被访问的邻接点 for(w=FirstAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w)) if(!visited[w]){ // 若 w尚未访问 visited[w]=TRUE; Visit(w); EnQueue(Q,w); }//if }//while}//BFSTraverse

Page 49: 第7章  图

BFS 算法效率分析 :

• 设图中有 n 个顶点, e 条边

• 邻接矩阵:对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为O(n2) 。

• 邻接表:总时间代价为 d0 + d1 + … + dn-1 = O(e) ,其中的 di 是顶点 i 的度。

DFS与 BFS 之比较:• 空间复杂度:相同, O(n)(借用了堆栈或队列);• 时间复杂度:只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。

49

Page 50: 第7章  图

无向图的连通性• 对于连通图,从图中任一顶点出发 , 利用深度优先搜索算法或广度优先搜索算法,便可访问到到图中的所有顶点深度优先搜索得到深度优先生成树广度优先搜索得到广度优先生成树

• 对于非连通图 , 则需从每一个连通分量中的任一个顶点出发进行遍历 , 可求得无向图的所有连通分量所有连通分量的生成树组成了非连通图的生成森林。

Page 51: 第7章  图

V8

V7V6V5V4

V3

V2

V1

V8

V7V6V5V4

V3

V2

V1

V8

V7V6V5V4

V3

V2

V1

深度优先生成树 广度优先生成树

Page 52: 第7章  图

V3

V1

V2

V4

V5

V6

V3

V1

V2

V4

V5

V6

深度优先遍历所经过的路径 广度优先遍历所经过的路径

非连通图的遍历 (见算法 7.4)

• 从每个连通分量选一起点,调用遍历算法(如DFS )完成各连通分量的深度先遍历。例:图 G 的深度优先序列: V1,V2,V3,V4,V5,V6,

广度优先序列: V1,V2,V3,V4,V5,V6,

Page 53: 第7章  图

7.3.3 遍历的应用例:求一条从顶点 v到顶点 s 的简单路径

• 以 V1 为起点的的深度优先序列:V1,V2,V4, V8, V5, V3,V6,V7

• V1到 V7 的简单路径:V1,V3,V6,V7

V8

V7V6V5V4

V3

V2

V1

Page 54: 第7章  图

void DFS_Simple( int v, int s, char *PATH)

{// 从第 v 个顶点出发深度优先遍历图 G,

// 求得一条从 v到 s 的简单路径,并记录在 PATH 中 visited[v] = TRUE; // 访问第 v 个顶点 Append(PATH, GetVex(v));

// 将第 v 个顶点添加到路径 PATH上 for (w=FirstAdjVex(v); w!=0&&!found ; w=NextAdjVex(v) )

if (w=s) { found = TRUE; Append(PATH, w); }

else if (!visited[w]) DFS_Simple(w, s, PATH);

// 对 v 的尚未访问的邻接顶点 w递归调用 DFS

if (!found) Delete (PATH); // 删除路径上最后一个顶点}

Page 55: 第7章  图

7.4 最小生成树应用问题

• 假设要在 n 个城市之间建立通讯联络网,最多可设置 n(n-1)/2 条线路。

• 连通 n 个城市只需要 n-1 条线路,如何在最节省经费的前提下建立这个通讯网?

问题抽象• 构造网的一棵最小代价生成树

(MST,Minimum Cost Spanning Tree) 在 n(n-1)/2 条带权的边中选取 n-1 条(不构成回路),使“权值之和”为最小。

Page 56: 第7章  图

MST 性质• 假设 N=(V, E) 是一个连通网, U 是顶点集 V 的一个非空子集。

• 若 (u, v) 是一条具有最小权值的边,其中 u U, ∈v V-U∈ ,则必存在一棵包含边 (u, v) 的最小生成树

最小生成树算法• 普里姆( Prim )算法• 克鲁斯卡尔( Kruskal )算法

Page 57: 第7章  图

一、普里姆( Prim )算法设 N =( V,E )是个连通网,设 U 为最小生成树的顶点

集, TE 为最小生成树的边集。算法思想:从单一顶点的树 T开始,往 T 中加入一条代价最小的边( u , v ),其中 u U, v V-U ∈ ∈ 。

构造步骤• (1)初始状态: U ={u0 }, (u0 V)∈ , TE={ },

• (2)从 E 中选择顶点分别属于 U、 V-U 两个集合、且权值最小的边 (u0, v0) ,将顶点 v0归并到集合 U 中,边 (u0, v0)归并到 TE 中;

• (3) 转 (2), 直到 U=V 为止。此时 T= (V,{TE})就是最小生成树。

Page 58: 第7章  图

V1

V2V4

V5

V3

V6

6 5

55

6

62

1

34

V1

V2V4

V5

V3

V6

1

4 2

5

3

Prim 算法示意图假设 n 个顶点分成两个集合

U (已落在生成树上的顶点集合)V-U (尚未落在生成树上的顶点集合)

在所有连通 U 中顶点和 V-U 中顶点的边中选取权值最小的边。

V1

V3

V6

V4V2

V5

Page 59: 第7章  图

记录从顶点集 U到 V- U 的代价最小的边的辅助数组

struct {

VertexType adjvex;

VRType lowcost;

} closedge[MAX_VERTEX_NUM];

Page 60: 第7章  图

Void MiniSpanTree_PRIM(Mgraph G, VertexType u){k=LocateVex ( G, u ); // 顶点 u 为构造生成树的起始点for ( j=0; j<G.vexnum; ++j ) // 辅助数组初始化

if (j!=k) closedge[j] = { u, G.arcs[k][j].adj }; closedge[k].lowcost = 0; // 初始, U= {u}for (i=1; i<G.vexnum; ++i) { // 在其余顶点中选择

k = minimum(closedge); // 求出 T 的下一个结点 (k)printf(closedge[k].adjvex, G.vexs[k]); // 输出生成树的边closedge[k].lowcost = 0; // 第 k 顶点并入 U 集for (j=0; j<G.vexnum; ++j)

if (G.arcs[k][j].adj < closedge[j].lowcost)closedge[j] = { G.vexs[k], G.arcs[k][j].adj };// 新顶点并入 U 后 ,更新辅助数组

}} 时间复杂度为 O(n2)

Page 61: 第7章  图

二、克鲁斯卡尔( Kruskal )算法算法思想

• 选择一条不会产生回路的,且具有最小代价的边加入生成树。

构造步骤• (1) 构造一个只含 n 个顶点的子图 T={V,};

• (2) 在 E 中选择一条具有最小权值的边。若该边的两个顶点落在不同的连通分量上,则在 T上加上这条边;否则舍弃该边。

• (3) 转 (2) 重复,直至加上 n-1 条边为止。

Page 62: 第7章  图

V1

V2V4

V5

V3

V6

6 5

55

6

62

1

34

V1

V2V4

V5

V3

V6

1

V1

V2V4

V5

V3

V6

1

2

V1

V2V4

V5

V3

V6

1

3 2

V1

V2V4

V5

V3

V6

1

4 23

V1

V2V4

V5

V3

V6

1

4 2

5

3

Kruskal 算法示意图

Page 63: 第7章  图

普里姆算法(Prim)

克鲁斯卡尔算法(Kruskal )

算法思想 顶点归并 边归并中间结果 树 森林时间复杂度 O(n2) O(eloge)

适应范围 稠密图 稀疏图

比较:两种最小生成树算法

Page 64: 第7章  图

7.5 有向无环图及应用有向无环图( DAG 图):没有回路的有向图。

• 许多应用问题可用有向无环图来表示和求解。例如:各种流程

– 工程流程– 生产过程中各道工序的流程– 程序流程– 课程的流程

Page 65: 第7章  图

例:课程流程图

编号 课程名称 预修课C1 程序设计基础 无C2 离散数学 C1C3 数据结构 C1,C2C4 汇编语言 C1C5 语言设计分析 C3,C4C6 计算机原理 C11C7 编译原理 C5,C3C8 操作系统 C3,C6C9 高等数学 无C10 线性代数 C9C11 普通物理 C9C12 数值分析 C1C9C10

C12

C10

C11

C9

C1

C2

C3

C4

C6

C5

C7

C8

Page 66: 第7章  图

对工程问题,人们至少关心如下两类问题:• 1 )工程能否顺序进行,即工程流程是否“合理”• 2 )完成整项工程至少需要多少时间,哪些子工程是影响工程进度的关键子工程?

下面分别就这两个问题讨论之。• 拓扑排序• 关键路径

Page 67: 第7章  图

7.5.1 AOV 网和拓扑排序问题一:

• 工程能否顺序进行,即工程流程是否“合理”假设以有向图表示一个工程的施工图,则图中不允许出现回路。

为求解工程流程是否“合理”• 通常用称为 AOV 网的有向图表示工程流程。

AOV 网(有向图)不存在有向回路• 当且仅当能对 AOV 网进行拓扑排序。

Page 68: 第7章  图

1.AOV 网 ( activity on vertex net )

• AOV 网:用顶点表示活动,边表示活动的顺序关系的有向图。

• 例 1 :某工程可分为V0、 V1、 V2、 V3、 V4、 V5、 V6 7 个子工程, 工程流程可用如下 AOV 网表示:顶点:表示子工程(也称活动)弧:表示子工程间的顺序关系。

V0 V3

V6

V2

V5

V4 V1

Page 69: 第7章  图

2.拓扑排序• 拓扑序列:有向图 D 的一个顶点序列称作一个拓扑序列,如果该序列中任两顶点 v 、 u ,若在 D中 v是 u 前驱,则在序列中 v 也是 u 前驱。

• 拓扑排序: 将有向图中顶点排成拓扑序列。对于有向图中没有限定次序关系的顶点,则可以人为加上任意的次序关系。

Page 70: 第7章  图

V0 V3

V6

V2

V5

V4 V1

3. 拓扑排序方法:• 1 )在有向图选一无前驱的顶点 v ,输出;• 2 )从有向图中删除 v 及以 v 为尾的孤;• 3 )重复 1 、 2 、直接全部输出全部顶点或有向图中不存在无前驱顶点例: V0,V1,V2,V3,V4,V5,V6

入度为零

相应弧头顶点的入度减 1

Page 71: 第7章  图

4. 拓扑排序算法• 1 )拓扑排序方法的等价描述:

i. 选择一入度为 0 顶点 v ,输出;ii. 将 v 邻接到的顶点的入度减 1 ;iii. 重复 i、 ii 直到输出全部顶点或有向图没有入度为 0

的顶点。

• 2 )拓扑排序过程中涉及的数据和操作: 数据:

– 有向图,顶点的入度; 操作:

– ( 1 ) 选择一入度为 0 顶点 v ,输出;– ( 2 ) 将 v 邻接到的顶点 u 的入度减 1 ;

Page 72: 第7章  图

V1 V4

V7

V3

V6

V5 V2

D.vertices

D.vexnum

D.arcnu

D.kind

V1V2V3V4V5V6V7

7

10

DG

0123456

2 4

56

33 4

55 6

• 3 )为算法的有关数据选择设计存储结构(1)ALGraph G

– 邻接表:存储有向图

Page 73: 第7章  图

0123456

indegree 0012232

S.base

6543210

10

S.top

存储顶点的入度的数组

存储入度为 0的顶点的栈

(2) int indegree[ ]

– 整型数组:存储顶点的入度(3) int S[ ]

– 整数栈:用于存储入度为 0 的顶点的编号

V1 V4

V7

V3

V6

V5 V2

Page 74: 第7章  图

4 )拓扑排序算法Status TopologicalSort(ALGraph G) {// 有向图 G采用邻接表存储结构。//若 G 无回路,则输出 G 的顶点的一个拓扑序列并返回

OK。// 否则 ERROR。 FindInDegree(G, indegree); //求各顶点入度 indegree[ ] InitStack(S); for(i=0; i<G. vexnum; ++i) if (! indegree[i]) Push(S, i); // 入度为 0 顶点的编号进栈

Page 75: 第7章  图

count=0; // 对输出顶点计数 while(! StackEmpty(S)){ Pop(S, i);

// 从零入度顶点栈 S 栈顶,获得一入度为零的顶点 i printf(i, G. vertices[i].data); ++count;

//输出 i 号顶点的数据,并计数 for (p=G. vertices[i]. firstarc; p; p=p->nextarc) { k=p->adjvex; if (!(- -indegree[k])) Push(S, k);

//对 i 号顶点邻接到的每个顶点入度减 1// 若入度减为 0 ,则入栈

}//for }//while if (count<G.vexnum) return ERROR; // 该有向图有回路 else return OK;}//TopologicalSort

时间复杂度为 O(n+e)

Page 76: 第7章  图

7.5.1 AOE 网和关键路径问题二:

• 完成整项工程至少需要多少时间,哪些子工程是影响工程进度的关键子工程?

通常可用称为 AOE 网的有向图表示工程流程。涉及求关键路径问题。

Page 77: 第7章  图

1. AOE 网 ( activity on edge net )

• AOE 网:用边表示活动,顶点表示事件的有向图。

V4

V1

V3 V8

V2 V7

V9

V6

V5a1=6

a2=4

a3=5

a4=1

a5=1

a6=2

a7=9

a8=7

a9=4

a10=2

a11=4

Page 78: 第7章  图

2. 关键路径• 关键路径:路径长度最长的路径。• 关键活动 : 该弧上的权值增加将使有向图上的最长路径的长度增加。

Page 79: 第7章  图

3.求关键路径举例

V4

V1

V3 V8

V2 V7

V9

V6

V5a1=6

a2=4

a3=5

a4=1

a5=1

a6=2

a7=9

a8=7

a9=4

a10=2

a11=4

Page 80: 第7章  图

• e(i) :表示活动 ai 的最早开始时间• l(i) :表示活动 ai 的最迟开始时间 ( 不推迟整个工期 )

e(i)=l(i) 的活动叫做关键活动 , 关键路径上的活动都是关键活动 .

若活动 ai 由弧 <j,k> 表示 , 其持续的时间记为dut(<j,k>), 则 • e(i) = ve(j)

• l(i) = vl(k) - dut(<j,k>)

Vj Vkai

Page 81: 第7章  图

• ve(j) :表示事件 vj 的最早发生时间• vl(j) : 表示事件 vj 的最迟发生时间 ( 不推迟整个工期 )

求 ve(j)和 vl(j) 分两步进行 :

• (1) 从 ve(源点 )=0开始向前递推 :ve(j) = max{ve(i) + dut(<i,j>)}

• (2) vl(汇点 )=ve(汇点 )开始向后递推 :vl(i) = min{vl(j) - dut(<i,j>)}

Vi Vjai

Vi Vjai

Page 82: 第7章  图

V4

V1

V3 V8

V2 V7

V9

V6

V5a1=6

a2=4

a3=5

a4=1

a5=1

a6=2

a7=9

a8=7

a9=4

a10=2

a11=4

事件j 1 2 3 4 5 6 7 8 9ve(j) 0 6 4 5 7 7 16 14 18vl(j) 0 6 6 8 7 10 16 14 18

活动i 1 2 3 4 5 6 7 8 9 10 11e(i) 0 0 0 6 4 5 7 7 7 16 14l(i) 0 2 3 6 6 8 7 7 10 16 14

ve(j) = max{ve(i) + dut(<i,j>)}vl(i) = min{vl(j) - dut(<i,j>)}

e(i) = ve(j)l(i) = vl(k) - dut(<j,k>)

Page 83: 第7章  图

整个工期为 18

关键路径有两条 :

• (V1,V2,V5,V7,V9),

• (V1,V2,V5,V8,V9)

关键活动有六个 :

• a1, a4, a7, a8, a10, a11

V1V8

V2 V7

V9V5a1=6

a4=1 a7=9

a8=7

a10=2

a11=4

Page 84: 第7章  图

算法思想• 以邻接表作存储结构• 从源点 V1 出发,令 ve[1]=0, 按拓扑序列求各顶点的 ve[i]

• 从汇点 Vn 出发,令 vl[n]=ve[n], 按逆拓扑序列求其余各顶点的 vl[i]

• 根据各顶点的 ve和 vl 值,计算每条弧的 e[i]和l[i], 找出 e[i]=l[i] 的关键活动

Page 85: 第7章  图

求事件的最早发生时间 ve算法Status Topologicalsort(ALGraph G, Stack &T){ //S 为零入度顶点栈, T 为拓扑序列顶点栈 ( 用于求 vl ) FindinDegree (G, indegree) ; // 建立入度为零的栈 S Initstack(T); count = 0; ve[0..G.vexnum-1]=0; while (!StackEmpty(S)) { Pop(S,j); Push(T,j); ++count; for (p=G.vertices[j]. firstarc; p; p=p->nextarc) { k = p->adjvex; dut=*(p->info) ; if (!(--indegree[k])) Push(S, k); if (ve[j]+ dut) > ve[k]) //infor 为活动持续时间 ve[k] = ve[j]+*(p->info); } } if (count < G.vexnum)return ERROR; else return OK;}

ve(j) = max{ve(i) + dut(<i,j>)}

Page 86: 第7章  图

求关键活动算法Status CriticalPath(ALGraph G){ //G 为有向图,输出 G 的关键活动 if(!Topologicalsort(G,T)) return ERROR; vl[0..G.vexnum-1]=ve[G.vexnum]; while (!StackEmpty(T)) {

Pop(T,j); for (p=G.vertices[j]. firstarc; p; p=p-

>nextarc) { k = p->adjvex; dut=*(p->info) ;

if (vl[k]-dut< vl[j]) vl[j] = vl[k]-*(p->info); } } //求事件的最迟发生时间 vl

Page 87: 第7章  图

for(j=0; j<G.vexnum; ++j)

//求 ee,el 和关键活动for (p=G.vertices[j]. firstarc; p; p=p->nextarc) {

k = p->adjvex; dut=*(p->info) ;

ee=ve[j]; el= vl[k]-dut; tag=(ee= =el)? ‘*’:‘’;

printf (j, k, dut, ee , el, tag);}

}

时间复杂度为 O(n+e)

Page 88: 第7章  图

比较AOV 网, AOE 网都能表示工程各子工程的流程,一个用顶点表示活动,一个用边表示活动。• AOV 网侧重表示活动的前后次序,• AOE 网除表示活动先后次序,还表示了活动的持续时间等因此可利用 AOE 网解决工程所需最短时间及哪些子工程拖延会影响整个工程按时完成等问题。

实际应用中采用那一种图,取决于要求解的问题。

Page 89: 第7章  图

习 题写出 7.3 所给图的深度优先和广度优先遍历序列

7.7

7.9,7.11

已知一个无向图用邻接矩阵存储,写出实现以下操作的算法:• DeleteVex(&G,v)

• InsertArc(&G,v,w)

• 对操作的具体定义见教材 P157。

Page 90: 第7章  图

7.6 最短路径

7.6.1 从某个源点到其余各顶点的最短路径• 给定带权有向图 G 和源点 v ,求从 v到 G 中其余各顶点的最短路径例 :求 v0到其余顶点的最短路径

始点 终点 最短路径 路径长度 v0 v1 无 v2 (v0,v2) 10 v3 (v0,v4,v3) 50 v4 (v0,v4) 30 v5 (v0,v4,v3,v5) 60V1

V5

V3

5 50

100

10

60

2010

30 V4

V2

V0

Page 91: 第7章  图

Vi

V0……

迪杰斯特拉 (Dijkstra) 算法迪杰斯特拉推出了一个按路径长度递增的次序求从源点到其余各点最短路径的算法。• 假设图中所示为从源点到其余各点之间的路径,则在这些路径中,必然存在一条长度最短者 (v0,vi)

• 长度次短的路径可能有两种情况 : 它可能是从源点直接到该点的路径 ;

也可能是,从源点到 vi, 再从 vi到该点

设 D [k] 为当前所求得的从源点到 k 的最短路径• D [k] = <源点到顶点 k 的弧上的权值 > 或者• D [k] = <源点到其它顶点的路径长度 >+ < 其它顶点到顶点 k 弧上的权值 >

Page 92: 第7章  图

迪杰斯特拉 (Dijkstra) 算法(1) 设置两个顶点的集合 T和 S;

• 集合 S 存放已找到最短路径的顶点• 集合 T 存放当前还未找到最短路径的顶点

(2)初始状态时 ,S只包含源点 v0 ;

(3)从 T 中选取某个顶点 vi( 要求 v0 到 vi 的路径长度最短 ) 加入到 S 中 ;

(4)S 中每加入一个顶点 vi, 都要修改顶点 v0到 T 中剩余顶点的最短路径长度值 ;

• 它们的值为原值与新值的较小者 ;

• 新值 =vi 的最短路径长度值 +vi到该顶点的路径长度

(5) 不断重复 (3) 和 (4), 直到 S 包含全部顶点 ;

Page 93: 第7章  图

带权邻接矩阵为 :

10 30 100 5 50 10 20 60 V2

V0V4

V1

V5

V3

5 50

100

10

60

2010

30

迪杰斯特拉 (Dijkstra)算法举例

Page 94: 第7章  图

求 v0 到其余顶点的最短路径最短路径的求解过程

终点 v1 v2 v3 v4 v5 vi

步骤 1 10 30 100 v2

(v0,v2) (v0,v4) (v0,v5)

步骤 2 60 30 100 v4

(v0,v2,v3) (v0,v4) (v0,v5)

步骤 3 50 90 v3

(v0,v4,v3) (v0,v4,v5)

步骤 4 60 v5

(v0,v4,v3,v5)

步骤 5

Page 95: 第7章  图

Void ShortestPath(Mgraph G,int v0,PathMatrix &P,ShortPathTable &D)//用 Dijkstra算法求有向图 G的 v0 顶点到其余顶点 v的// 最短路径 P[v] 及其带权路径长度 D[v]//final[v]为 TRUE ,表示已求得从 v0到 v 的最短路径

{ for(v=0;v<G.vexnum;++v)//初始化{ final[v]=FALSE; D[v]=G.arcs[v0][v]; for(w=0;w<G.vexnum;++w)P[v][w]=FALSE; if(D[v]<INFINITY)

{P[v][v0]=TRUE; P[v][v]=TRUE;}}D[v0]=0; final[v0]=TRUE;//v0 加入 S 集

Page 96: 第7章  图

for(i=1;i<G.vexnum;++i){min=INFINITY; for(w=0;w<G.vexnum;++w)

if(!final[w]) if(D[w]<min){v=w; min=D[w];}//找出离 v0 最近的顶点 v

final[v]=TRUE; //v 加入 S 集 for(w=0;w<G.vexnum;++w)//更新 D[w]和 P[w]

if(!final[w]&&(min+G.arcs[v][w]<D[w])){D[w]=min+G.arcs[v][w]; P[w]=P[v]; P[w][w]=TRUE; }//if

}//for}//ShortestPath.DLJ 时间复杂度为 O(n2)

Page 97: 第7章  图

7.6.2 每一对顶点间的最短路径7.6.2 每一对顶点间的最短路径

• 给定带权有向图 G,求 G 中每个顶点到其余各顶点的最短路径例 :求有向网 G 的每一对顶点的最短路径

V0

V2

V1

6

2

4113

有向网 G

0 4 116 0 23 0

邻接矩阵

Page 98: 第7章  图

Floyd 算法

(1)递推产生一个矩阵序列 A-1,A0,A1,...,Ak,...An

• 其中 Ak[i, j] 表示从顶点 vi到 vj的路径上所经过的顶点序号不大于 k 的最短路径长度

(2)初始时 , A-1为图的邻接矩阵 .

(3)Ak [i,j]=min{Ak-1[i,j], Ak-1[i,k]+Ak-1[k,j]}

(0<=k<=n-1)0 4 116 0 23 0

A-1=0 4 116 0 23 7 0

A0=

0 4 66 0 23 7 0

A1=

V0

V2

V1

6

2

4113

有向网 G

0 4 65 0 23 7 0

A2=

Page 99: 第7章  图

Floyd 算法初始化: D[v][w]和 P[v][w][u]

for(u=0;u<G.vexnum;u++) //u 为新加入路径的顶点 for(v=0;v<G.vexnum;v++) for(w=0;w<G.vexnum;w++) if( D[v][w]>D[v][u]+D[u][w] )

{D[v][w] = D[v][u]+D[u][w] ;

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

P[v][w][i]= P[v][u][i] || P[u][w][i]

}

算法结束: D 即为所有点对的最短路径矩阵时间复杂度为 O(n3)

Page 100: 第7章  图

图图

存储结构

遍 历

邻接矩阵邻 接 表十字链表邻接多重表

深度优先搜索 DFS

广度优先搜索 BFS

有向 ( 无环 ) 图有向 ( 无环 ) 图拓扑排序关键路径最短路径

Prim算法Kruskal算法最小生成树

Dijkstra算法Floyd算法

本章小结