Transcript Document
第六章 图
§ 6.1 图的概念
图(Graph)——是一种结点之间为多对多关系的数据
结构。
逻辑特征是:可以有任意个开始结点和任意个终端结
点,其余各个结点可以有任意个前趋和任意个后继。
图中的结点常称为顶点。
图的逻辑结构可以用二元组表示:
Graph=(V,E)
V是顶点(vertex)的集合;
E是边(edge)的集合。
有向图(Digraph)——若图G中的每条边都是有方
向的,则称图G为有向图。
例
v1
v2
v1
v3
v2
G1
v3
G2
G1 = (V1, E1)
V1 = {v1, v2, v3}
E1 = {<v1, v2>, <v2, v1>, <v2, v3>}
G2 = (V2, E2)
V2 = {v1, v2, v3}
E2 = {<v1, v2>, <v2, v1>, <v1, v3>, <v3, v1>, <v2, v3>, <v3, v2>}
无向图(Undigraph)——若图G中的每条边都是没有方
向的,则图G称为无向图。
例
v1
v2
v1
v4
v2
v3
v3
G3
G4
G3 = (V3, E3)
V3= {v1, v2, v3, v4}
E3= {(v1, v2), (v1, v3), (v1, v4), (v2, v3), (v2, v4)}
G4 = (V4, E4)
V4= {v1, v2, v3 }
E4= {(v1, v2), (v1, v3), (v2, v3) }
邻接点与关联边
有向图中,若<x,y>是有向图中的一条边,则顶点x
和顶点y称为邻接点。称x邻接到y或y邻
接于x,同时称边<x,y>是顶点x和顶点y
相关联的边(Incident);
无向图中,若(x,y)是无向图中的一条边,则顶点
x和顶点y互为邻接点,并且称边(x, y)是
顶点 x和顶点y相关联的边。
顶点的度
有向图中,顶点的度分成入度与出度
入度:该顶点入边的数目
出度:该顶点出边的数目
无向图中,顶点的度为与该顶点相连的边数
子图——设有图G=(V,E) ,如果满足:
V’V
E’E
E'中边所邻接的点均在V'中出现
则 G‘= (V’,E‘)也是一个图,并且称之为G的子图。
有向完全图——n个顶点的有向图最大边数是n(n-1)
无向完全图——n个顶点的无向图最大边数是n(n-1)/2
路径——有向图G=(V,E)中,若存在一个顶点序列
vp,vi1,vi2,…vin,vq,使得<vp,vi1>,<vi1,vi2 >,…,<vin,vq>
均是图中的边,则称此序列是从顶点vp到vq一条路径。
路径长度——该路径上经过边的数目。
回路——一条路径第一个顶点和最后一个顶点相同的 叫~
简单路径——序列中顶点不重复出现的路径叫~
简单回路——除了第一个顶点和最后一个顶点外,其余顶
点不重复出现的回路叫~
有根图——若存在一个顶点v,从该顶点到其余各个
顶点都有路径,则称此图为有根图
连通——从顶点x到顶点y有一条路径,则说x和y是连
通的
连通图——图中任意两个顶点都是连通的叫连通图
连通分量——无向图G的极大连通子图称为G的连通
分量
强连通图——若G中任意两个顶点都是强连通的,则
称图G是强连通图
强连通分量 ——有向图G的极大强连通子图称为G的
强连通分量
权——与图的边相关的数值叫权值。
网络——边上带权的图称为网络
例
v1
v1
v2
v3
v2
有向完全图
v3
无向完全图
例
v1
v2
v1
v3
G1
v1
(a)
v2
v3
(b)
图与子图
v2
v3
(c)
例
v1
例
v1
v2
v4
v2
v3
v3
顶点v2的出度为2,
顶点v1的度为3
入度为1,
定点v4的度为2
度为3
例
v1
v2
v3
v4
v5
路径:v1,v2,v4,v3,v1
路径长度:4
简单路径:v1,v2,v4
回路:v3,v5,v4,v3,v1,v2,v4
简单回路:v3,v5,v4,v3
例
v1
v3
一个连通图
v2
v4
v5
v1
例
v3
v2
例
v1
v2
v3
G5
一个强连通图
v4
v5
v6
v7
v1
v2
v3
v4
v5
v6
v7
(a)
非连通图G5 和它的两个连通分量
(b)
6.2 图的存储结构
邻接矩阵——表示顶点之间邻接关系的矩阵
设G=(V,E)是具有n个顶点的图,则G的邻接矩阵是
一个n阶方阵A,A中元素的值aij可以定义为:
aij =
1: <vi,vj> 或(vi,vj)是E中的边
0: <vi,vj> 或(vi,vj)不是E中的边
v5
v4
A
B
v2
v3
C
D
v1
(a)
(b)
数组
r1
0
1
2
3
4
5
v1
v2
v3
v4
v5
0
0
A 1= 0
0
0
1
0
1
1
0
0
1
0
1
0
0
0
0
0
1
1
0
0
0
0
数组
r2
0
1
A
2
B
3
C
D
4
A2=
0
1
1
1
1
0
0
1
1
0
0
1
1
1
1
0
特点:
无向图的邻接矩阵对称;有n个顶点的无向图需存储空间为n²
有向图邻接矩阵不一定对称;有n个顶点的有向图需存储空
间为n²
无向图中顶点Vi的度是邻接矩阵A中第i行元素之和
有向图中,
顶点Vi的出度是A中第i行元素之和
顶点Vi的入度是A中第i列元素之和
网的邻接矩阵可定义为:
Wij : i ≠ j且<vi,vj>或(vi,vj)是E中的边
aij =
∞:
i ≠ j且<vi,vj>或(vi,vj)不是E中的边
0 : i=j
例
v5
1
2
4
v1
v4
3
3
5
v2
6
5
A3=
0
3
∞
4
2
3
0
6
5
∞
∞
6
0
5
3
4
5
5
0
1
2
∞
3
1
0
v3
网络及其邻接矩阵
可以得到邻接矩阵存储结构C语言描述如下:
#define n 5
/* 图的顶点数 */
#define e 6
/* 图的边数 */
#define max 10000
/* 设置一个极大数无穷大 */
typedef char vextype; /* 顶点类型 */
typedef int adjtype;
/* 权值类型 */
typedef struct
{ vextype vertex[n+1];
/* 顶点数组 */
adjtype edge[n+1][n+1];
/* 邻接矩阵 */
}adj_matrix;
邻接表
邻接表法(Adjacency List)是图的一种链式存储结构
顶点采用顺序方式进行存储,
用n个单链表来存储图中的边,第i个单链表是所有与第i个顶点相关
联的边链接而成的,称此单链表为第i个顶点的边表,边表中的每个
结点称为边表结点。
在顶点的顺序表中,每个元素增加一个指针域用来存放各个边表的
头指针,称此顺序表为顶点表,而顺序表中的每个元素称为顶点表
结点。
顶点表和各顶点的边表一起组成图的邻接表。
边表结点
adjvex
next
顶点表结点
vertex
邻接表存储结构C语言描述如下:
typedef struct node
{
int adjvex;
/*
struct node *next;
/*
}edgenode;
/*
typedef struct
{
vextype vertex;
/*
edgenode *link;
/*
}vexnode;
/*
vexnode adjlist[n+1];
link
邻接点域 */
指针域 */
定义边表结点 */
顶点域 */
指针域 */
定义顶点表结点 */
例
v5
v4
v2
v3
v1
vertex
link
0
1
2
3
4
5
有向图及其邻接表
adjvex next
v1
2
v2
v3
v4
v5
3
^
2
^
顶点表
2
4
^
出边表
5
^
3
^
例
A
B
无向图及其邻接表
C
vertex
D
link
0
adjvex next
1
A
2
3
2
B
C
D
1
4
^
1
4
^
1
2
3
4
顶点表
边表
4
^
3
^
特点
无向图中顶点Vi的度为第i个单链表中的结点数
有向图中
顶点Vi的出度为第i个单链表中的结点个数
顶点Vi的入度为整个单链表中邻接点域值是i的结点个数
逆邻接表:有向图中对每个结点建立以Vi终点的单链表
例
v5
v4
vertex
link
1
v1
^
2
v2
v3
v4
v5
0
v1
v2
v3
3
4
5
有向图及其逆邻接表
顶点表
adjvex next
1
3
2
4
5
1
^
入边表
4
^
^
边集数组
邻接表法(Adjacency List)是图的一种链式存储结构
边集数组(edgeset array)是图的一种顺序存储方式,利用一维数组
来存储图中所有的边,数组中的每个元素用来存储图中的一条边,包
括:始点、终点的序号及权值,该数组中所含元素的个数要大于等于
图中边的条数。
有向图及其边集数组
5
v1
v2
0
1
2
3
4
5
6
7
8
3
10
6
6
v3
v4
18
8
12
v5
(a)
typedef struct edge
{
int fromvex;
int endvex;
int weight;
}edgeset;
edgeset ge[e+1];
起点
1
1
2
2
2
3
3
4
终点
2
3
3
4
5
4
5
5
权值
5
6
10
3
8
6
18
12
(b)
/* 边的始点域 */
/* 边的终点域 */
/* 边的权值域 */
/* 定义边集数组类型 */
/* 边集数组全局量 */
§ 6.3 图的遍历
深度优先遍历(DFS)
方法:对于给定的图G,假设初始时所有顶点均未被访问过,则
可从G中任选一顶点vi做为初始出发点,深度优先搜索可定义为:
访问出发点vi,置访问标记为1,然后依次从vi的未被访问过的邻
接点vj出发,继续进行深度优先搜索,直至图中所有和vi有路径
相通的顶点均被访问过。很显然图的深度优先搜索过程是递归的。
它的特点是尽可能先对图从纵深方向进行搜索,故称为深度优先
搜索。
v1
v2
v4
v3
v5
v6
v7
v8
DFS序列为:v1,v2,v4,v8,v5,v3,v6,v7。
深度优先遍历算法
递归算法
v1
v2
v4
v3
v5
v8
v6
v7
0
1
1
0
0
0
0
0
1
0
0
1
1
0
0
0
1
0
0
0
0
1
1
0
0
1
0
0
0
0
0
1
0
1
0
0
0
0
0
1
0
0
1
0
0
0
1
0
0
0
1
0
0
1
0
0
0
0
0
1
1
0
0
0
void DFS(int i)
{ int j;
printf ("输出序号为%d的顶点: %c\n",i,adj->vertex[i]);
visited[i]=1;
/* 标记vi已经访问过 */
for (j=1;j<=n;j++)
/* 依次搜索vi的邻接点 */
if((adj->edge[i][j])&&(!visited[j]))
DFS(j);}
vertex
0
v1
v2
v4
v3
v5
v8
v6
v7
link
adjvex next
1
v1
3
2
2
v2
5
4
1
3
v3
7
6
1
4
v4
8
2
5
v5
8
2
6
v6
7
3
7
v7
6
3
8
v8
5
4
void DFSL(int i)
{ edgenode *p;
printf("输出序号为%d的顶点: %c\n",i,adjlist[i].vertex);
visited [i]=1;
/* 标记vi已经访问过 */
p=adjlist[i].link; /* p为vi的边表头指针 */
while (p)
/* 依次搜索vi的邻接点 */
{ if(!visited[p->adjvex])
DFSL(p->adjvex);
p=p->next;
}
}
在邻接矩阵上实现遍历的过程:
v1 v2 v3 v4 v5 v6 v7 v8
1
0
1
1
0
0
0
0
0
1
1
5
1
0
0
1
1
0
0
0
0
1
0
0
0
0
1
1
0
2
0
1
0
0
0
0
0
1
4
(a)
0
1
0
0
0
0
0
1
6
0
0
1
0
0
0
1
0
0
0
1
0
0
1
0
0
7
3
0
0
0
1
1
0
0
0
2
v1
5
v2
4
^
3
2
5
4
1
^
7
6
1
^
8
2
^
8
2
^
7
3
^
3
^
4
^
2
3
v3
4
v4
6
5
v5
6
v6
7
v7
6
8
v8
5
3
7
(b)
生成树
连通图G的一个子图如果是一棵包含G的所有顶点的
树,则该子图称为G的生成树(Spanning Tree) 。
v1
v2
v3
v4
v5
v6
v7
v8
BFS生成树
DFS生成树
v1
v1
v2
v4
v5
v8
v2
v3
v6
v7
v4
v3
v5
v8
v6
v7
广度优先遍历(BFS)
方法:对于给定图G,假设初始时的所有顶点均未被访问过,
从图G中任选一顶点vi为初始出发点,广度优先搜索遍历可定义
为:首先访问出发点vi,接着依次访问vi的所有的邻接的未被访
问过的点w1, w2,…, wt,然后,再依次访问与w1,w2,…,wt相
邻接的未被访问过的顶点。依此类推,直至图中所有和初始出发
点vi有路径相通的顶点都已访问到为止。显然,此方法的特点是
尽可能先对横向进行搜索,故称之为广度优先搜索。
v1
v2
v4
v3
v5
v6
v7
v8
BFS序列为:v1,v2,v3,v4,v5,v6,v7,v8
广度优先遍历算法
在广度优先遍历中,先被访问的顶点,其邻接点亦先被访问,
所以在算法的实现中需要使用一个队列,用来依次记住被访问过的
顶点。
算法开始时,将初始点Vi访问后插入队列中,以后每从队列
中删除一个元素,就依次访问它的每一个未被访问过的邻接点,并
令其进队。这样当队列为空时,表明所有与初始点有路径相通的顶
点都已访问完毕,算法到此结束。
adjvex
例
1
2
3
5
f
4
next
1
1
4
2
2
5
1
^
3
3
5
1
^
4
4
5
1
^
5
5
4
3
f
1
0 1 2 3 4 5
4
0 1 2 3 4 5
r
r
遍历序列:1
adjvex next
3
遍历序列:1 4
2
^
2
^
f
4 3
0 1 2 3 4 5
r
遍历序列:1 4 3
adjvex
例
3
adjvex next
3
1
1
4
2
2
5
1
^
4 3
3
5
1
^
4
4
5
1
^
5
5
4
3
1
2
next
5
f
f
4 3 2
0 1 2 3 4 5
r
遍历序列:1 4 3 2
2
^
2
^
f
3 2
0 1 2 3 4 5
r
遍历序列:1 4 3 2
3 2 5
0 1 2 3 4 5
r
遍历序列:1 4 3 2 5
vexdata firstarc
例
1
2
3
4
1
1
4
2
2
5
1
^
3
3
5
1
^
4
4
5
1
^
5
5
4
3
5
f
adjvex next
3
r
遍历序列:1 4 3 2 5
5
0 1 2 3 4 5
r
遍历序列:1 4 3 2 5
^
2
^
f
f
2 5
0 1 2 3 4 5
2
0 1 2 3 4 5
r
遍历序列:1 4 3 2 5
邻接表法广度优先:
void BFSL(int k) /* 用adjlist存储 */
{ int i;
edgenode *p;
SETNULL(Q);
printf("输出序号为%d的顶点: %c\n",k,adjlist[k].vertex); /* 访问出发点vk */
visited[k]=1; /* 标记vk已经访问过 */
ENQUEUE (Q,k); /* 顶点vk的序号k入队 */
while(!EMPTY(Q)) /* 队列非空执行 */
{ i= DEQUEUE(Q);/* 队头元素顶点序号出队 */
p=adjlist[i].link;
while (p!=NULL)
{ if(!visited[p->adjvex])
{ printf("输出序号为%d的顶点: %c\n",p->adjvex,adjlist[p->adjvex].vertex);
visited[p->adjvex] =1;
ENQUEUE(Q,p->adjvex);
}
p=p->next;
}
}
}
生成树
连通图G的一个子图如果是一棵包含G的所有顶点的
树,则该子图称为G的生成树(Spanning Tree) 。
深度优先生成树与广度优先生成树
说明
一个图可以有许多棵不同的生成树
所有生成树具有以下共同特点:
生成树的顶点个数与图的顶点个数相同
生成树是图的极小连通子图
一个有n个顶点的连通图的生成树有n-1条边
生成树中任意两个顶点间的路径是唯一的
在生成树中再加一条边必然形成回路
含n个顶点n-1条边的图不一定是生成树
v1
v2
v4
v3
v5
v6
v7
v8
BFS生成树
DFS生成树
v1
v1
v2
v4
v5
v8
v2
v3
v6
v7
v4
v3
v5
v8
v6
v7
§6.4最小生成树
问题提出
7
1
13
2
7
9
5
要在n个城市间建立通信联络网,
17
12
顶点——表示城市
3
权——城市间建立通信线路所需花费代价
18
希望找到一棵生成树,它的每条边上的权值之和(即建立
该通信网所需花费的总代价)最小———最小代价生成树
5
6
24
10
问题分析
n个城市间,最多可设置n(n-1)/2条线路
n个城市间建立通信网,只需n-1条线路
问题转化为:如何在可能的线路中选择n-1条,能把
所有城市(顶点)均连起来,且总耗费
(各边权值之和)最小
定义:连通网络的所有生成树中边上权值之和最小的生成树称
为最小生成树(Minimun Spanning Tree)
4
MST性质:
假设G=(V, E)是一个连通网络,U为顶点集V
的一个非空子集。若边(u,v)是所有的一个端
点在U中(即u∈U),另一个端点不在U中(
即v∈V-U)的这些边里面,权值最小的一条
,则一定存在一棵G的最小生成树包括此边
(u,v)。
构造最小生成树方法
方法一:普里姆(Prim)算法
算法思想:设G=(V, E)是连通网,T=(U,TE)是G的
最小生成树,其中U是T的顶点集,TE是T的边集,U和
TE的初值均为空集。
初始令U={u0},(u0V), TE=
在所有uU,vV-U的边(u,v)E中,找一条代价最小的边
(u0,v0)
将(u0,v0)并入集合TE,同时v0并入U
重复上述操作直至U=V为止,则T=(V,TE)为G的最小生
成树
14
1
6
有有有有有有有有有有
8
2
1
6
18
21
16
6
5
2
6
16
8
5
12
12
3
15
(a)
4
3
15
(b)
4
用Prim算法构造最小生成树的过程
1
1
(a)
1
6
2
6
6
2
2
1
16
6
5
2
6
8
(b)
6
8
1
(c)
6
8
1
16
6
5
2
6
16
8
5
12
4
(d)
(e)
12
3
15
(f)
4
生成树T
的边集数
组数组变
化过程
始点 终点 权值
T
T
0
1
2
3
4
5
始点 终点 权值
1
1
1
1
1
2
3
4
5
6
6
∞
∞
∞
14
1
2
3
4
5
始点 终点 权值
0
1
2
3
4
5
1
2
1
1
2
2
6
4
5
3
6
8 √
∞
∞
21
0
2
3
4
5
6 √
∞
∞
∞
14
1
2
3
4
5
1
2
3
4
5
2
6
5
4
3
6
8
16
12
18
(g)调整T[4]和T[5]
1
2
3
4
5
6
21
∞
∞
8
0
1
2
1
6
6
2
6
4
5
3
6
8
∞
16
18
始点 终点 权值
1
2
3
4
5
1
2
6
1
6
2
6
5
4
3
6
8
16 √
∞
18
(f)T[3]和T[4]交换
T
始点 终点 权值
0
0
1
2
6
5
6
2
3
4
5
6
始点 终点 权值
T
(e)调整T[3]到T[5]
T
1
2
1
1
2
(c)调整T[2]到T[5]
0
始点 终点 权值
1
2
3
4
5
6
始点 终点 权值
(d)T[2]到T[5]交换
T
1
1
1
1
1
(b)T[1]和T[1]交换
T
始点 终点 权值
0
0
(a)初始化
T
T
1
2
6
5
6
2
6
5
4
3
6
8
16
12 √
18
(h)T[4]和T[4]交换
1
2
3
4
5
1
2
6
5
4
2
6
5
4
3
6
8
16
12
15
(i)调整T[5]
方法二:克鲁斯卡尔(Kruskal)算法
算法思想:设连通网G=(V,E),令最小生成树T=(U,TE)
初始状态U=V,TE=
将图G中的边按权值从小到大的顺序依次选取,若选取
的边使生成树T不形成回路,则把它并入TE中,保留作
为T的一条边;若选取的边使生成树T形成回路,则将其
舍弃。
依此类推,直至TE中包含n-1条边为止,此时的T 即为最
小生成树。
用Kruskal算法构造最小生成树的过程
1
6
1
1
6
6
6
2
5
3
2
4
5
3
(a)
1
6
6
8
2
5
4
(d)
4
(c)
1
6
6
8
2
5
12
3
5
3
4
1
8
2
(b)
6
6
6
16
8
2
5
12
12
3
15
(e)
4
3
15
(f)
4
§ 7.5 拓扑排序
问题提出:学生选修课程问题
顶点——表示课程
有向边——表示先决条件,若课程i是课程j的先决条件,
则图中有边<i,j>
学生应按怎样的顺序学习这些课程,才能无矛盾、顺利地
完成学业——拓扑排序
定义
AOV网——用顶点表示活动,用有向边表示活动的先后
关系的有向图称为顶点活动网(Activity On Vertex
network),简称AOV网
若<vi,vj>是图中有向边,则vi是vj的直接前驱;vj是vi的直接后继
AOV网中不允许有回路
§ 6.5 最短路经
最短路径问题通常是指如何从图中某一顶点(称为源点
)到达另一顶点(称为终点)的多条路径中,找到一条路
径,使得此路径上经过的各边上的权值总和达到最小
。
最短路径问题通常可以分成四种不同情况:
单源点、单目标点最短路径问题;
单源点、多目标点最短路径问题;
多源点、单目标点最短路径问题;
多源点、多目标点最短路径问题。
我们讨论最常见的两种情况:单源点最短路径和任意两
点间的最短路径。
单源最短路经
5
从源点1到其余各顶点的最短路径
60
100
30
1
4
10
20
10
2
50
3
迪杰斯特拉(Dijkstra)算法:
初始化:集合S中只有一个源点,其余顶点都在集合V-S中。此
时S中源点的距离值(最短路径)为0;V-S中各个顶点的距离值
为:只经过源点到达该顶点的当时最短路径,即从源点到达该顶
点的边长(无边时,距离值为∞)。当某点的距离值不等于∞时
,可以得到该点的路径(一条边)。
首先,从V-S中选择一个距离值最小的顶点v,将其加入到S中,
扩充集合S。此时该点的距离值就是最短路径长度。
然后,对集合V-S中剩余顶点的距离值进行修正。方法是:假设u
是V-S中的一个顶点,u点当前的距离值为len_u,而新加入S的
点m的最短路径len_m加上边<u,m> 的长度为L,若L<len_u,
则u点当前的距离值修正为:len_u=L。同时修正路径,即在m点
的路径后面加上u点即可。
最后,重复(2)、(3)步,直到所有顶点全部进入集合S,即
V=S为止。
5
60
100
30
1
4
10
1. 初始化:S={ 1},V-S={ 2,3,4,5}。
各点的距离值及路径为:
1组顶点: { 1 }
最短路径: 0
经过路径: 1 1
2组顶点: { 2
距离值: 10
经过路径: 1 2
3
∞
1 3
20
10
2
4
30
1 4
50
5}
100
1 5
2. 从V-S中选择距离值最小的顶点2,加入到S中。此时S={ 1
,2},V-S={ 3,4,5}。然后对V-S中各点的距离值进行修正
,修正后各点的距离值及路径为:
1组顶点: { 1
2}
10
最短路径: 0
经过路径: 1 1 1 2
2组顶点: { 3
距离值: 60
经过路径: 1 2 3
4
30
1 4
5}
100
1 5
3
3.再从V-S中选择距离值最小的顶点4,加入到S中。此时S={ 1,2,4},
V-S={ 3,5}。修正V-S中的点的距离值为:
2
4}
1组顶点: { 1
10
30
最短路径: 0
经过路径: 1 1 1 2 1 4
2组顶点:{ 3
5}
90
距离值: 50
1 4 3 1 4 5
经过路径:
4. 继续从V-S中选择距离值最小的顶点3,加入到S中。此时S={ 1,2,
3,4},V-S={ 5}。修正V-S中的点的距离值为:
1组顶点:{ 1
2
3
4}
10
50 30
最短路径: 0
经过路径: 1 1 1 2 1 4 1 4 3
2组顶点:{ 5 }
距离值: 60
1 4 3 5
经过路径:
5.最后从V-S中选择距离值最小的顶点5,加入到S中。此时S={ 1,2,
3,4,5},V-S={ },算法结束。
2
3
4
5}
1组顶点: { 1
10 50 30
60
最短路径: 0
经过路径: 1 1 1 2 1 4 1 4 3 1 4 3 5
2组顶点:{ }
5
60
100
30
1
4
A7 =
10
20
10
2
50
0
∞
∞
∞
∞
∞
50
0
20
∞
10
0
∞
∞
∞
30
∞
∞
0
∞
100
∞
10
60
0
3
存前点”方式存储结构
frontnode
pathlength
源点到顶点2的最短路
径为10,到达2的前一
个顶点为1
0
1
0
0
2
3
1
4
10
50
4
1
30
5
3
60
源点到顶点3的最短路
径为50,到达v的前一
个顶点为4
源点到顶点4的最短路
径为30,到达4的前一
个顶点为1
源点到顶点5的最短路
径为60,到达5的前一
个顶点为3
存储结构C语言描述:
typedef struct
{ int prenode;
int pathlength;
}shortestpath;
int flag[n+1];
shortestpath sp[n+1];
flag
frontnode pathlength
0
flag
frontnode
pathlength
1
1
0
0
0
0
1
2
1
1
0
10
60
30
100
0
1
0
0
0
0
1
2
3
4
5
0
1
0
1
1
0
10
max
30
100
1
2
3
4
5
(a)初始化
flag
frontnode
(b)选择顶点2
pathlength
flag
frontnode
pathlength
1
1
1
1
0
0
1
4
1
3
0
10
50
30
60
0
0
0
1
4
1
4
1
1
0
1
0
1
2
3
4
5
0
10
50
30
90
(c)选择顶点4
flag
frontnode
2
3
4
5
1
1
1
1
1
0
1
4
1
3
2
3
4
5
(d)选择顶点3
pathlength
0
1
1
0
10
50
30
60
(e)选择顶点5
任意两点间最短路经
Floyd算法的实现是通过矩阵迭代来完成的。
有向图及其邻接矩阵
4
1
4
3
0 1 ∞ 4
2
1
6
8
9
2
3
5
(a)
∞ 0 9 2
3 5 0 8
∞ ∞ 6 0
(b)
0
1
2
3
4
0
∞
3
∞
1
0
5
∞
1
∞
9
0
6
4
2
8
0
0
∞
3
∞
1
0
4
∞
0
1
2
3
4
0
0
3
0
1
0
3
0
0
2
0
4
2
∞
9
0
6
4 0 1 10 3
2 ∞ 0 9 2
7 3 4 0 6
0 ∞ ∞ 6 0
1
1
2
3
0
0
0
3
0
1
0
1
0
3
0
2
0
4
0 1 10
12 0 9
3 4 0
9 10 6
2
1
2
1
0
0
0
3
0
1
0
1
0
2
2
0
4
4
3
2
6
0
0 1
11 0
3 4
9 10
3
2
2
2
0
0
3
3
3
1
0
1
3
2
2
0
4
9
8
0
6
3
2
6
0
4
2
2
2
0
0
4
3
3
1
0
1
3
4
4
0
4
2
2
2
0
§6.6 拓扑排序
拓扑排序是有向无环图(directed acycline graph)上
的重要运算。拓扑排序的目的是将有向无环图中所有的
顶点排成一个线性序列,通常称为拓扑序列。
拓扑序列必须满足如下条件:对一个有向无环图G=(
V,E),若顶点u,v∈V,且u到v有路径,则在此线性
序列中,顶点u必排列在顶点v之前。
AOV网 :用顶点表示活动,顶点之间的有向边表示活
动之间的先后关系,从而将实际问题转化为一个有向图
,称其为顶点活动网(Activity On Vertex network),
简称AOV网。
C4
C1
C3
C8
C9
C5
C7
C2
C6
可以得到拓扑序列:
(C1,C3,C3,C4,C5,C6,,C7,C8,C9,C10)
和
(C2,C6,C1,C5,C7,C10,C4,C3,C8,C9)
C10
拓扑排序的方法
在有向图中选一个没有前驱的顶点且输出之
从网中删除该顶点及所有出边
重复上述两步,直至全部顶点均已输出;或者当网中
不存在入度为0的顶点为止。
AOV网求拓扑排序的过程
v1
v2
v1
v2
v2
v2
v4
v3
v4
v3
v3
v3
v6
v5
v5
v5
v5
(a) 初始状态
(b) 输出v6后
(c) 输出v1后
v4
v2
v5
v5
(d) 输出v4后 (e) 输出v5后 (f) 输出v2后
一个AOV网的拓扑序列不是唯一的
算法实现
以邻接表作存储结构
每次都需要查找入度为0的顶点并删除出边,在邻接
表上删除出边很容易,而要查找入度为0的顶点需要
遍历所有的单链表,较麻烦。为此,增加一个存放顶
点入度的域(id),先将每个顶点的入度求出后依次
存放在该域中。
重复上述操作直至栈空为止,拓扑排序完毕。
id
id
id
id
id
id
id
id
top
top
v1
0
0
v2
2
v3
0
0 √
2
2
1
1
1
1
v4
2
2
v5
3
v6
0
(a)
初始化
top
top
0
top
4
4
4
1
0
0
3
2
2
1
1
1
1 √
1
(b)
v1和v6入栈后
top
√
(c)
输出v6后
(d)
输出v1后
√
√
top
0
√
0
√
0
√
4
√
4
√
4
√
4
√
4
√
4
√
0
√
0
√
0
√
1
√
0
top
1
√
(e)
输出v3后
1
√
(f)
输出v2后
0
1
√
(g)
输出v4后
(h)
输出v5后
§6.7 关键路径
关键路径则是AOE网(Activity On Edge network)上
的典型运算
在AOE网中,有且仅有一个开始的顶点,代表工程的
开始,称为源点;
有且仅有一个终端的顶点,代表工程的结束,称为汇
点。
一个AOE网示例
v2
6
a 1=
源点
v1
:
v5
a3 =
12
6
a 6=
v4
a2 =
10
6
a 4=
v3
a8 =
a5=12
4
v7
a7 =
4
v6
a 9=
8
a10=10
v8
汇点
AOE网在估算某项工程的完成时间方面非常有用。通
常AOE网所研究的问题是:
完成整个工程至少需要多少时间?
为缩短完成工程所需的时间, 应当加快哪些活动?
由于AOE网中有些活动可以并行进行,所以整个工程
的最短完成时间取决于从源点到汇点的最长路径的长
度。我们把从源点到汇点的最长的路径称作关键路径
(Critical path),而关键路径上的活动称为关键活动
(Critical activity)。只有适当地加快关键活动,才能
缩短整个工期。
为了寻找关键活动,确定关键路径,我们首先了解几个与计算关
键活动有关的量:
事件vi 可能的最早发生时间Ve(i):假设源点为v1,则Ve(i)就是
从源点v1到顶点vi 的最长路径的长度。
事件vi 允许的最迟发生时间Vl(i):是在保证汇点Vn 在Ve(n) 时刻
完成的前提下,事件vi允许的最迟发生时间。
活动ak 可能的最早开始时间 e(k):设活动ak 在边< vi,vj >上,
则e(k)是从源点v1到顶点vi 的最长路径的长度。因此,e(k) =
Ve(i)。
活动ak允许的最迟开始时间 l(k) :l(k)是在不会引起时间延误的
前提下, 该活动允许的最迟开始时间。l(k) = Vl(j) - dur(<i, j>)。其
中, dur(<i, j>)是完成ak 所需的时间。
时间剩余量 l(k)-e(k):表示活动ak 的最早可能开始时间和最迟允
许开始时间的时间剩余量。当l(k) == e(k)时,表示活动ak是没有
时间剩余量的,也就是关键活动。
为求得AOE网中的e(k) 与 l(k), 需要先求得事件
的最早发生时间Ve(i)和事件的最迟发生时间Vl(i)
求Ve(i)的递推公式:
Ve(1) = 0
Ve(i) = max{ Ve(j) + dur(<j, i>)}
求Vl(i)的递推公式:
Vl(n) = Ve(n)
Vl(i)= min{Vl(j)-dur(<i,j>)}
v2
6
a 1=
源点
v1
a2 =
10
6
a 4=
v2
6
a 1=
v1
v3
4
v7
a7 =
4
v6
a5=12
a 9=
a10=10
v8
汇点
8
a3 =
12
v4
a2 =
10
a8 =
6
a 6=
v4
v3
源点
v5
a3 =
12
a5=12
v7
a7 =
4
v6
a 9=
8
a10=10
v8
汇点