数据结构与算法——第7章-图-拓扑排序算法(7.13)

一 概述

1
2
3
1.拓扑排序
2.拓扑排序演示
3.示例代码

二 拓扑排序

2.1 有向无环图

1
2
3
4
拓扑排序指的是将有向无环图(又称“DAG”图)中的顶点按照图中指定的先后顺序进行排序。

例如,图 1 中的两个图都是有向无环图,都可以使用拓扑排序对图中的顶点进行排序,两个图形的区别是:
左图中的 V2 和 V3 之间没有明确的前后顺序;而右图中任意两个顶点之间都有前后顺序。

2.2 偏序和全序

1
2
3
4
5
6
7
所以,左图中顶点之间的关系被称为“偏序”关系;右图中顶点之间的关系被称为”全序“关系。

在有向无环图中,弧的方向代表着顶点之间的先后次序,
例如从 V1 指向 V2 的弧表示在进行排序时 V1 在前, V2 在后。

全序是偏序的一种特殊情况。对于任意一个有向无环图来说,通过拓扑排序得到的序列首先一定是偏序,
如果任意两个顶点都具有前后顺序,那么此序列是全序。

三 拓扑排序演示

3.1 遵循两个原则

1
2
3
对有向无环图进行拓扑排序,只需要遵循两个原则:
1. 在图中选择一个没有前驱的顶点 V;
2. 从图中删除顶点 V 和所有以该顶点为尾的弧

3.2 演示

例如,在对图 1 中的左图进行拓扑排序时的步骤如图 2 所示:

3.3 AOV网

1
2
3
4
有向无环图如果顶点本身具有某种实际意义,例如用有向无环图表示大学期间所学习的全部课程,
每个顶点都表示一门课程,有向边表示课程学习的先后次序,
例如要先学《程序设计基础》和《离散数学》,然后才能学习《数据结构》。
所以用来表示某种活动间的优先关系的有向图简称为“AOV网”。

3.4 拓扑排序序列

1
2
3
4
5
6
7
8
9
进行拓扑排序时,首先找到没有前驱的顶点 V1,如图 2(1)所示;
在删除顶点 V1 及以 V1 作为起点的弧后,继续查找没有前驱的顶点,此时, V2和 V3 都符合条件,可以随机选择一个,
例如图 2(2) 所示,选择 V2 ,然后继续重复以上的操作,直至最后找不到没有前驱的顶点。
所以,针对图 2 来说,拓扑排序最后得到的序列有两种:
1.V1 -> V2 -> V3 -> V4
2.V1 -> V3 -> V2 -> V4

如果顶点之间只是具有偏序关系,那么拓扑排序的结果肯定不唯一;
如果顶点之间是全序关系,那么拓扑排序得到的序列唯一。

四 示例代码

4.1 分析

1
2
3
4
5
在编写程序解决拓扑排序的问题时,大致思路为:首先通过邻接表将 AOV 网进行存储,由于拓扑排序的整个过程中,
都是以顶点的入度为依据进行排序,所以需要根据建立的邻接表统计出各顶点的入度。

在得到各顶点的入度后,首先找到入度为 0 的顶点作为拓扑排序的起始点,然后查找以该顶点为起始点的所有顶点,
如果入度为 1,说明如果删除前一个顶点后,该顶点的入度为 0,为拓扑排序的下一个对象。

4.2 实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 20//最大顶点个数
#define VertexType int//顶点数据的类型
typedef enum{false,true} bool;
typedef struct ArcNode{
int adjvex;//邻接点在数组中的位置下标
struct ArcNode * nextarc;//指向下一个邻接点的指针
}ArcNode;

typedef struct VNode{
VertexType data;//顶点的数据域
ArcNode * firstarc;//指向邻接点的指针
}VNode,AdjList[MAX_VERTEX_NUM];//存储各链表头结点的数组

typedef struct {
AdjList vertices;//图中顶点及各邻接点数组
int vexnum,arcnum;//记录图中顶点数和边或弧数
}ALGraph;
//找到顶点对应在邻接表数组中的位置下标
int LocateVex(ALGraph G,VertexType u){
for (int i=0; i<G.vexnum; i++) {
if (G.vertices[i].data==u) {
return i;
}
}
return -1;
}
//创建AOV网,构建邻接表
void CreateAOV(ALGraph **G){
*G=(ALGraph*)malloc(sizeof(ALGraph));

scanf("%d,%d",&((*G)->vexnum),&((*G)->arcnum));
for (int i=0; i<(*G)->vexnum; i++) {
scanf("%d",&((*G)->vertices[i].data));
(*G)->vertices[i].firstarc=NULL;
}
VertexType initial,end;
for (int i=0; i<(*G)->arcnum; i++) {
scanf("%d,%d",&initial,&end);

ArcNode *p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=LocateVex(*(*G), end);
p->nextarc=NULL;

int locate=LocateVex(*(*G), initial);
p->nextarc=(*G)->vertices[locate].firstarc;
(*G)->vertices[locate].firstarc=p;
}
}
//结构体定义栈结构
typedef struct stack{
VertexType data;
struct stack * next;
}stack;
//初始化栈结构
void initStack(stack* *S){
(*S)=(stack*)malloc(sizeof(stack));
(*S)->next=NULL;
}
//判断链表是否为空
bool StackEmpty(stack S){
if (S.next==NULL) {
return true;
}
return false;
}
//进栈,以头插法将新结点插入到链表中
void push(stack *S,VertexType u){
stack *p=(stack*)malloc(sizeof(stack));
p->data=u;
p->next=NULL;
p->next=S->next;
S->next=p;
}
//弹栈函数,删除链表首元结点的同时,释放该空间,并将该结点中的数据域通过地址传值给变量i;
void pop(stack *S,VertexType *i){
stack *p=S->next;
*i=p->data;
S->next=S->next->next;
free(p);
}
//统计各顶点的入度
void FindInDegree(ALGraph G,int indegree[]){
//初始化数组,默认初始值全部为0
for (int i=0; i<G.vexnum; i++) {
indegree[i]=0;
}
//遍历邻接表,根据各链表中结点的数据域存储的各顶点位置下标,在indegree数组相应位置+1
for (int i=0; i<G.vexnum; i++) {
ArcNode *p=G.vertices[i].firstarc;
while (p) {
indegree[p->adjvex]++;
p=p->nextarc;
}
}
}
void TopologicalSort(ALGraph G){
int indegree[G.vexnum];//创建记录各顶点入度的数组
FindInDegree(G,indegree);//统计各顶点的入度
//建立栈结构,程序中使用的是链表
stack *S;
initStack(&S);
//查找度为0的顶点,作为起始点
for (int i=0; i<G.vexnum; i++) {
if (!indegree[i]) {
push(S, i);
}
}
int count=0;
//当栈为空,说明排序完成
while (!StackEmpty(*S)) {
int index;
//弹栈,并记录栈中保存的顶点所在邻接表数组中的位置
pop(S,&index);
printf("%d",G.vertices[index].data);
++count;
//依次查找跟该顶点相链接的顶点,如果初始入度为1,当删除前一个顶点后,该顶点入度为0
for (ArcNode *p=G.vertices[index].firstarc; p; p=p->nextarc) {
VertexType k=p->adjvex;
if (!(--indegree[k])) {
//顶点入度为0,入栈
push(S, k);
}
}
}
//如果count值小于顶点数量,表明该有向图有环
if (count<G.vexnum) {
printf("该图有回路");
return;
}
}

int main(){
ALGraph *G;
CreateAOV(&G);//创建AOV网
TopologicalSort(*G);//进行拓扑排序
return 0;
}

4.3 使用图示

1
例如使用上述完整代码解决下图的有向无环图时,拓扑排序的结果为:

4.4 运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
6,8
1
2
3
4
5
6
1,2
1,4
1,3
3,2
3,5
4,5
6,4
6,5
6 1 4 3 2 5

五 参考

  • C语言中文网—拓扑排序算法及C语言实现