图
一、图的定义和术语
1、图的定义
图是一种典型的非线性结构(多对多),由一个非空顶点集合和一个描述顶点之间关系即边的有限集合组成的一种数据结构。记作G=(V,E)其中:
(1)集合V={v1,v2,v3…vn}是一个有限非空集合,集合V中的元素称为图G的顶点(或称为结点),并且集合V称为图G的顶点集(或称为结点集)
(2)集合E是顶点集V中不同元素的非有序对偶(即{vi,vj},其中vi不等于vj)的集合,这些对偶称为图G的边(或称为弧),并且将集合E称为图G的边集。
(3)按照图中的边是否有方向性,可以分为无向图与有向图如图所示
- 在无向图中,用圆括号表示两个顶点之间的边。(vi,vj)表示顶点vi和顶点vj之间有一条边无向直接连线,也称为边。对于图G2可以表示为:
G2=(V,E)
V={v1,v2,v3,v4,v5};
E={(v1,v2),(v1,v4),(v2,v3),(v2,v5),(v3,v4),(v3,v5),(v4,v5)}
若图中边有方向,则用尖括号表示两个顶点之间的首尾关系,有向边也称为弧,及<vi,vj>,vi称为弧尾,vj称为弧头
对于图G1可表示为:
G2=(V,E)
V={v1,v2,v3,v4}
E={<v1,v2>,<v1,v3>,<v3,v4>,<v4,v1>}
2、图的相关术语
(1)无向完全图,在无向图G=(V,E)中,如果任意两个不同的顶点都是邻接的,那么就称该图为无向完全图 如图所示可以证明在一个含有N个顶点的无向完全图中有n*(n-1)/2边。
(2)有向完全图。在一个有向图G=(V,E)中,如果任意两顶点之间都有方向互为相反的两条弧相连接,则称该图为有向完全图,如图所示,在一个含有n个顶点的有向完全图中,有n(n-1)条边。
(3)顶点的度,入度,出度。在无向图中:顶点拥有的边数,称为该顶点的度。记作TD(v)。
在有向图中:
一个顶点拥有的弧头的数目称为入度,记作ID(v)
一个顶点拥有的弧尾的数目称为出度,记作OD(v)
一个顶点拥有的入度+出度,称为度,记作TD(v)
如在图G2中有:
TD(v1)=2 TD(v2)=3 TD(v4)=3 TD(v3)=3 TD(v5)=3
在图G1中有:
ID(v1)=1 OD(v1)=2 TD(v1)=3
ID(v2)=1 OD(v2)=0 TD(v2)=1
ID(v3)=1 OD(v3)=1 TD(v3)=2
ID(v4)=1 OD(v4)=1 TD(v4)=2
可以证明,对于具有N个顶点、e条边的图,顶点vi的度与顶点的个数以及边的数目满足关系:
(4)稀疏图:有很少边或弧的图(e<nlogn) 。
(5)稠密图:有较多边或弧的图。
(6)网:边/弧带权的图。
(7)邻接:有边/弧相连的两个顶点之间的关系。存在(vi,vj),则称v;和v;互为邻接点;存在<vi, vj>,则称vi邻接到vj,vj邻接于vi
(8)关联(依附):边/弧与顶点之间的关系。存在(vi, vj)/ <vi; vj>,则称该边/弧关联于vi和Vj
例1、问:当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是何形状?
答:如图 是树!而且是-棵有向树!
(9)路径:接续的边构成的顶点序列。
(10)路径长度:路径上边或弧的数目/权值之和。
(11)回路(环):第一个顶点和最后一个顶点相同的路径。如图
(12)简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。如图
(13)简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
(14)连通图(强连通图)在无(有) 向图G=(V, {E} )中,若对任何两个顶点v、u都存在从v到u的路径,则称G是连通图(强连通图)。如图所示
(15)权与网:图中边或弧所具有的相关数称为权。表明从一个顶点到另-个顶点
的距离或耗费。带权的图称为网。
(16)子图:设有两个图G= (V, {E}) 、G1= (V1, {E1}),若V1c V, E1c E,则称G1是G的子图。
例2 :下面图中谁是谁的子图
答:(b)、© 是(a)的子图
(17)连通分量(强连通分量):
-
无向图G的极大连通子图称为G的连通分量。极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。如图所示
-
有向图G的极大强连通子图称为G的强连通分量。极大强连通子图意思是:该子图是G的强连通子图,将D的任何不在该子图中的顶点加入,子图不再是强连通的。如图所示
-
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边子图不再连通。
(18)生成树:包含无向图G所有顶点的极小连通子图,所有顶点均由边连接在一起,但不存在回路的图,如图所示:
➢一个图可以有许多棵不同的生成树
所有生成树具有以下共同特点 -
生成树的顶点个数与图的顶点个数相同;
-
个有几个顶点的连通图的生成树有(n-1条边;
-
生成树是图的极小连通子图,去掉一条边则非连通;
-
在生成树中再加一条边必然形成回路。
-
生成森林:对非连通图,由各个连通分量的生成树的集合。
(19)无向图的生成树 设图G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边的集合, B(G) 是遍历图时未经过的边的集合。显然,G1(V, T)是图G 边的集合, B(G) 是遍历图时未经过的边的集合。显然,G1(V, T)是图G 的极小连通子图。即子图G1是连通图G的生成树。如图所示:
二、图的存储结构
1、邻接矩阵
(1)图的邻接矩阵
邻接矩阵是表示顶点之间相邻关系的矩阵。假设图G=(V,E)是具有n个顶点的图,即V={v0,v1,v2,v3…vn},则G的邻接矩阵是具有如下性质的n阶方阵:
如图所示为无向图及其邻接矩阵表示:
(2)网的邻接矩阵
若G是网,则邻接矩阵可定义为:
其中,wij表示边(v1,vj)或者<vi,vj>上的权值:∞表示一个计算机允许的、大于所有边的权值的数
如图所示为有向网带权图及其邻接矩阵
如图所示为无向带权图及其邻接矩阵
(3)图的邻接矩阵表示法的特点
- 无向图的邻接矩阵是对称的,而有向图的邻接矩阵不一定对称。
- 对于无向图,顶点vi的度是邻接矩阵中第i行(或第i列)的非零元素的个数。
- 对于有向图,顶点vi的度是邻接矩阵中第i行和第i列的非零元素的个数之和。
- 用邻接矩阵方法存储图,很容易确定图中任意两个项点之间是否有边相连,但要确定图中的边数,则必须按行、按列对每个元素进行检查,所花费的时间代价很大。这是邻接矩阵存储图的局限性。
(4)图的邻接矩阵的算法
#define MAX 100 //最大顶点数
#include<stdio.h>
typedef struct {
int e,n;//顶点数与边数
char vexs[MAX];//顶点数组
int visited[MAX];//深度遍历辅助数组
int egrs[MAX][MAX];//邻接矩阵数组
}MGrage;
void CreateMGrage(MGrage *h){
int i,j,k;
char ch1,ch2;
printf("请输入顶点数:");
scanf("%d",&h->e);
printf("请输入边数:");
scanf("%d",&h->n);
for(i=0;i<h->e;i++){
getchar();
printf("输入第%d个顶点:",i+1);
scanf("%c",&h->vexs[i]);
}
for(i=0;i<h->e;i++){//邻接矩阵元素置为o0
for(j=0;j<h->e;j++){
h->egrs[i][j]=0;
}
}
for(k=0;k<h->n;k++){
getchar();
printf("建立第%d条边(格式X,X):",k+1);
scanf("%c,%c",&ch1,&ch2);
for(i=0;i<h->e;i++){
for(j=0;j<h->e;j++){
if(ch1==h->vexs[i]&&ch2==h->vexs[j]) {
h->egrs[i][j]=1;
h->egrs[j][i]=1;
}
}
}
}
}
void ScMGrage(MGrage *h){//输出邻接矩阵
int i,j;
printf("\n图的邻接矩阵(无向):\n");
for(i=0;i<h->e;i++){
printf("%c",h->vexs[i]);
for(j=0;j<h->e;j++){
printf("%5d",h->egrs[i][j]);
}
printf("\n");
}
}
有向图的话把h->egrs[j][i]=1;
删除即可
运行结果
(5)网的邻接矩阵的算法
把scanf函数输入值中加上权值scanf("%c,%c,%d",&ch1,&ch2,&x);
在把h->egrs[i][j]=1;
改为h->egrs[i][j]=x;
同理把在h->egrs[j][i]=1;
改为h->egrs[j][i]=x;
,即可,有向网的话把h->egrs[j][i]=x;
删除即可
运行结果:
2、邻接表
(1)邻接表的链式表示法
-顶点:按编号顺序将顶点数据存储在一维数组中;
-关联同一顶点的边(以顶点为尾的弧) :用线性链表存储
- 邻接点域,存放与vi邻接的顶点在表头数组中的位置。
(2)无向图邻接表
特点: - 邻接表不唯一
- 若无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图。
- 无向图中顶点( vi的度为第i个单链表中的结点数。
(3)有向图的邻接表
特点: - 找出度易,找入度难。
- 顶点vi的出度为第i个单链表中的结点个数。
- 顶点vi的入度为整个单链表中邻接点域值是i-1的结点个数。
- 顶点vi的入度为第i个单链表中的结点个数。
- 顶点vi的出度为整个单链表中邻接点域值是i-1的结点个数。
(4)无向图的邻接表的算法
#define MAX 100//最大顶点
#include<stdio.h>
typedef struct ArcNode{//定义边表结点
int adjvex;//邻接点域
struct ArcNode *nextarc;//指向下一邻接点的指针域
}ArcNode;
typedef struct VNode{//定义顶点表结点
char data;//顶点域
ArcNode *firstarc;//指向第一条边结点
}VNode,AdjList[MAX];
typedef struct{
int Visited[MAX];//深度优先算法辅助数组
AdjList vertices; //邻接表头结点数组
int vexnum,arcnum;//顶点数,边数
}ALGraph;
void CreateUDC(ALGraph *h){
int i,j,k,l;
char ch1,ch2;
ArcNode *p1,*p2;
printf("请输入顶点数:");
scanf("%d",&h->vexnum);
printf("请输入边数:");
scanf("%d",&h->arcnum);
for(i=0;i<h->vexnum;i++){
getchar();
printf("请输入第%d顶点:",i+1);
scanf("%c",&h->vertices[i]);//读入顶点信息
h->vertices[i].firstarc=NULL; //点的边表头指针设为空
}
for(k=0;k<h->arcnum;k++){
getchar();
printf("建立第%d条边(格式A,B):",k+1);
scanf("%c,%c",&ch1,&ch2);
for(l=0;l<h->vexnum;l++){
if(ch1==h->vertices[l].data){
i=l;
}
if(ch2==h->vertices[l].data){
j=l;
}
}
p1=new ArcNode;//生成一个新的边结点*p1
p1->adjvex=j;//邻接点序号为j
p1->nextarc=h->vertices[i].firstarc;//将新结点*p1插入顶点vi的边表头部
h->vertices[i].firstarc=p1;
p2=new ArcNode;//生成另一个对称的新的边结点*p2
p2->adjvex=i;//邻接点序号为i
p2->nextarc=h->vertices[j].firstarc;//将新结点* p2插入顶点vj的边表头部
h->vertices[j].firstarc=p2;
}
}
void ScUDC(ALGraph *h){
int i;
ArcNode *P;
printf("\n图的邻接表(无向):\n");
for(i=0;i<h->vexnum;i++){
printf("%C",h->vertices[i].data);
P=h->vertices[i].firstarc;
while(P!=NULL){
printf("-->%d",P->adjvex);
P=P->nextarc;
}
printf("\n");
}
}
有向图删除p1或者p2即可
运行结果:
(5)网向图的邻接表的算法
把无向图中的scanf函数加上输入权值:scanf("%c,%c,%d",&ch1,&ch2,&x);
在把p1->adjvex=j;
改为p1->adjvex=x;
同理把p2->adjvex=i;
改为p2->adjvex=x;
即可把无向图的邻接表变成无向网的邻接表,有向网删除p1或者p2即可变成有向网的邻接表
运行结果
3、邻接矩阵与邻接表的关系
(1)联系:邻接表中每个链表对应于邻接矩阵中的一-行,链表中结点个数等于一行中非零元素的个数。
(2)区别:①对于任一-确定的无向图, 邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯- - (链接次序与顶点编号无关)。
②邻接矩阵的空间复杂度为O(n^2),而邻接表的空间复杂度为O(n+e)。
(3)用途:邻接矩阵多用于稠密图;而邻接表多用于稀疏图。
三、图的遍历
从已给的连通图中某一顶点出发,沿着- -些边访遍图中所有的顶点, 且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。
1、深度优先遍历(DFS)
(1)方法:
- 在访问图中某-起始顶点v后,由v出发,访问它的任一-邻接顶点 w1
- 再从w1出发,访问与w1邻接但还未被访问过的顶点w2;
- 然后再从w2出发,进行类似的访问,…
- 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点u为止。
- 接着,退回-步,退到前一-次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。
- 如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;
- 如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。
例3:
- 连通图的深度优先遍历类似于树的先根遍历
(2)深度优先遍历的算法与实现
邻接矩阵表示的无向图深度遍历实现如图:
算法:
结构体在图的邻接矩阵的算法中
void visit(MGrage *h){//初始化辅助数组
int i;
for(i=0;i<h->e;i++){
h->visited[i]=0;
}
}
void DFS(MGrage *h,int x){//深度优先遍历
int i;
h->visited[x-1]=1;//访问第x个顶点
for(i=0;i<h->n;i++){
if((h->egrs[x-1][i]!=0)&&(h->visited[i]==0)){//依次检查邻接矩阵x所在的行
printf("%5c",h->vexs[i]);
DFS(h,i+1);//i是v的邻接点,如果i未访问,则递归调用DFS
}
}
}
运行结果:
有向图与无向图算法相同
邻接表的深度优先算法
void DFSvisitde(ALGraph *h)
{
int i;
for(i=0;i<h->vexnum;i++){
h->Visited[i]=0;
}
}
void DFS(ALGraph *h,int x){
ArcNode *p;
h->Visited[x]=1;
printf("(%d,",x);
printf("%c)",h->vertices[x].data);
p=h->vertices[x].firstarc;
while(p!=NULL){
if(h->Visited[p->adjvex]==0){
DFS(h,p->adjvex);
}
p=p->nextarc;
}
}
运行结果:
(3)深度优先算法效率分析
- 用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在的行,时间复杂度为O(n^2)。
- 用邻接表来表示图,虽然有2e个表结点,但只需扫描、e个结点即可完成遍历,加上访问n个头结点的时间,时间复杂度为0(n+ e)。
结论: - 稠密图适于在邻接矩阵_上进行深度遍历;
- 稀疏图适于在邻接表上进行深度遍历。
2、广度优先遍历(BFS)
(1)方法:
方法:从图的某- -结点出发, 首先依次访问该结点的所有邻接表点点Vi1, Vi2, … Vin再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点重复此过程,直至所有顶点均被访问为止。
例4:
(2)算法与实现
连通图
结构体在图的邻接表算法中
void BFS(ALGraph *h ,int x){
int i,v,visited[MAX];
int qu[MAX],front=0,rear=0;
ArcNode *p;
for(i=0;i<h->vexnum;i++){
visited[i]=0;
}
printf("(%d,",x);
printf("%c)",h->vertices[x].data);
visited[x]=1;
rear=(rear+1)%MAX;
qu[rear]=x;
while(front!=rear){
front=(front+1)%MAX;
v=qu[front];
p=h->vertices[v].firstarc;
while(p!=NULL){
if(visited[p->adjvex]==0){
visited[p->adjvex]=1;
printf("(%d,",p->adjvex);
printf("%c)",h->vertices[p->adjvex].data);
rear=(rear+1)%MAX;
qu[rear]=p->adjvex;
}
p=p->nextarc;
}
}
}
运行结果:
(3)BFS算法效率分析
- 如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n个元素),总的时间代价为O(n^2)。
- 用邻接表来表示图,虽然有2e个表结点,但只需扫描e个结点即可完成遍历,加上访问n个头结点的时间,时间复杂度为O(n+e)。
3、DFS与BFS算法比较
- 空间复杂度相同,都是O(n)(借用了堆栈或队列) ;
- 时间复杂度只与存储结构(邻接矩阵或邻接表) 有关,而与搜索路径无关。
四、图的连通性
1、最小生成树
最小生成树:给定一个无向网络在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树也叫最小代价生成树。
(1)构造最小生成树
构造最小生成树的算法很多,其中多数算法都利用了MST的性质。MST性质:设N= (V, E)是一个连通网,U是顶点集V的一-个非空子集。若边(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u, v)的最小生成树。如图所示
N = (V, {E}) V = {v1, v2, v3, v4, v5, v6} E = {(v1, v2), (v1, v3), (v1, v4), (v2, v3), (v2, v5), (v3, v4), (v3, v5), (v3, v6),(v4, v6), (v5, v6)} U = {v1}
- MST性质解释:
在生成树的构造过程中,图中n个顶点分属两个集合: - 已落在生成树上的顶点集: U
- 尚未落在生成树上的顶点集: V-U
接下来则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。
(2)构造最小生成树方法一-: 普里姆(Prim)算法
算法思想:
➢设N=(V, E)是连通网,TE是N.上最小生成树中边的集合。
➢初始令U={u0}, (u0∈V), TE={}。
➢在所有u∈U, v∈V-U的边(u, v)∈E中,找一条代价最小的边(u0, v0)。
➢将(uo, v0)并入集合TE,同时V0并入U。
➢重复上述操作直至U=V为止,则T=(V, TE)为N的最小生成树。
(3)普里姆(Prim)算法与实现
结构体在网的邻接矩阵算法中
无向网并用邻接矩阵存储时的普里姆(Prim)算法
//普里姆算法
void Prim(MGrage *h){
int i,j,k, min,lowcost[MAX],closest[MAX];
for(i=0;i<h->e;i++){//从邻接矩阵第零行开始
lowcost[i]=h->egrs[0][i];//将第0行的数组信息赋值到lowcost数组中
closest[i]=0;
}
closest[0]=-1;//设置初值-1
for(i=0;i<h->e;i++){//从U之外求离U中某一顶点最近的顶点
min=MAXS;k=i;
for(j=0;j<h->e;j++){//从邻接矩阵第1行开始找各权值中最小值
if(lowcost[j]<min&&closest[j]!=-1){
min=lowcost[j];//最小值设为当前边的权值
k=j;//此边依附顶点
}
}
if(closest[k]!=-1){
printf("(%c,%c) 权值%d\n",h->vexs[closest[k]],h->vexs[k],lowcost[k]);//打印生成树该边及其权值
}
closest[k]=-1;//K加入到U中
for(j=0;j<h->e;j++){//设顶点k为下次查找的起始点
if(closest[j]!=-1&&h->egrs[k][j]<lowcost[j]){
lowcost[j]=h->egrs[k][j];
closest[j]=k;
}
}
}
}
运行结果:
(4)构造最小生成树方法二:克鲁斯卡尔(Kruskal)算法:
算法思想:
➢设连通网N= (V, E),令最小生成树初始状态为只有n个顶点而无边的非连通图T=(V, {}),每个顶点自成一个连通分量。
➢在E中选取代价最小的边,若该边依附的顶点落在T中不同的连通分量上(即:不能形成环) ,则将此边加入到T中;否则,舍去此边,选取下-条代价最小的边。➢依此类推,直至T中所有顶点都在同f连通分量上为止。
(4)克鲁斯卡尔(Kruskal)算法与实现:
结构体在网的邻接矩阵算法中
无向网并用邻接矩阵存储时的克鲁斯卡尔(Kruskal)算法
typedef struct {
char u;//记录边的起始点
char v;//记录边的终点
int w;//记录边的权值
}Edge;
//克鲁斯卡尔算法
void sort(MGrage *h){
int i,j;
Edge t;
for(i=0;i<h->n-1;i++){//为边表进行从小到大排序算法
for(j=i+1;j<h->n;j++){
if(E[i].w>E[j].w){
t=E[i];
E[i]=E[j];
E[j]=t;
}
}
}
}
int seeks(int set[],int v){
int i=v;
while(set[i]>0){//查看顶点V在哪个连通集合
i=set[i];
}
return(i);
}
void Krusjal(MGrage *h,Edge E[]){//E所表示的图是按权值从小到大排序
int set[MAX];//辅助数组
int v1,v2,i;
for(i=0;i<MAX;i++){
set[i]=0;//给set中每一个元素赋
}
i=0;//i表示待获取的生成树中的 边数,初值为 1
while(i<h->n){
v1=seeks(set,E[i].u);//确定顶点V所在的连通集
v2=seeks(set,E[i].v);
if(v1!=v2){//当V1,V2不在同一顶点集合,确定该边应当选入生成树
printf("(%c,%c) 权值 %d\n",E[i].u,E[i].v,E[i].w);
set[v1]=v2;//将v2加入到v1的集合中
}
i++;
}
}
运行结果:
(5)普里姆(Prim)算法与克鲁斯卡尔(Kruskal)算法的比较
算法名 | 普里姆算法 | 克鲁斯卡尔算法 |
---|---|---|
算法思想 | 选择点 | 选择边 |
时间复杂度 | O(n^2) (n为顶点数) | O(eloge) (e为边数) |
适应范围 | 稠密图 | 稀疏图 |
五、最短路径问题
1、最短路径概念
最短路径问题是图的又一个比较典型的应用问题。例如,某一地区的一 个交通网, 给定了该网内的n个城市以及这些城市之间的相通公路的距离,问题是如何在城市A和城市B之间找一条最近的通路。如果将城市用顶点表示,城市间的道路用边表示,道路的长度则作为边的权值,那么,这个问题就可归结为在网中,求点A到点B的所有路径中,边的权值之和最短的那一条路径。 这条路径就称为两点之间的最短路径,并称路径上的第-个顶点为源点(Sourse), 最后一个顶点为终点(Destination)。
2、通过算法求最短路径(Dijistra算法)
void Dijkstra(MGrage *h,int v){//迪杰斯特拉算法求最短路径
int dist[MAX],path[MAX],s[MAX];
int mindis,i,j,u,pre;
for(i=0;i<h->e;i++){
dist[i]=h->egrs[v][i];
s[i]=0;
if(h->egrs[v][i]<MAXS){
path[i]=v;
}else{
path[i]=-1;
}
}
s[v]=1;path[v]=0;
for(i=0;i<h->e;i++){
mindis=MAXS;
u=-1;
for(j=0;j<h->e;j++){
if(s[j]==0&&dist[j]<mindis){
u=j;
mindis=dist[j];
}
}
if(u!=-1){
s[u]=1;
for(j=0;j<h->e;j++){
if(s[j]==0){
if(h->egrs[u][j]<MAXS&&dist[u]+h->egrs[u][j]<dist[j]){
dist[j]=dist[u]+h->egrs[u][j];
path[j]=u;
}
}
}
}
}
printf("\nDijkstra算法求解如下:");
for(i=0;i<h->e;i++){
if(i!=v){
printf("\n%c->%c:",h->vexs[v],h->vexs[i]);
if(s[i]==1){
printf("路径长度为%2d,",dist[i]);
pre=i;
printf("路径逆序为:");
while(pre!=v){
printf("%c,",h->vexs[pre]);
pre=path[pre];
}
printf("%c",h->vexs[pre]);
}else{
printf("不存在路径\n");
}
}
}
}
运行结果:
六、拓扑排序
1、有向无环图及其应用
有向无环图:无环的有向图,简称DAG图
(1)AOV网:用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网.(拓扑排序)
AOE网:用一个有向图表示一 一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网,简称为AOE网.(关键路径)
AOV网的特点:
- 若从i到j有一条有向路径,则i是j的前驱;j是i的后继。
- 若<i,j>是网中有向边,则i是j的直接前驱; j 是i的直接后继。
- AOV网中不允许有回路,因为如果有回路存在,则表明某项活动以自己为先决条件,显然这是荒谬的。
2、拓扑排序
在AOV网没有回路的前提下,我们将全部活动排列成一- 个线性序列,使得若AOV网中有弧<i, j>存在,则在这个序列中,i- -定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。
(1)拓扑排序的方法:
- 在有向图中选一个没有 前驱的顶点且输出之。
- 从图中删除该顶点和所有以它为尾的弧。
- 重复上述两步,直至全部顶点均已输出;或者当图中不存在无前驱的顶点为止。
例5:写出下图的拓扑排序
解:拓扑序列: - C1, C2, C3, C4, C5, C7, C9, C10, C11,C6,C12, C8
- C9, C10, C11, C6, C1, C12, C4, C2,C3, C5, C7, C8
注意:一个AOV网的拓扑序列不是唯一的
(2)拓扑排序的应用
AOV网中是否存在环方法:对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环。