0%

图的应用--拓扑排序

什么是拓扑排序

拓扑排序经常用于完成有依赖关系的任务的排序。
举个例子:
一个软件工程专业的学生必须学习系列的基本课程,其中有些课程是基础课,独立于其他课程,而另一些课程必须先学完先修课程才可以开始后序课程。这个关系可以用有向图表示。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190716130910951.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Nvbmdzb25nTA==,size_16,color_FFFFFF,t_70 =600x)
学生必须按照拓扑有序的顺序来安排学习计划,这样才能保证学习任何一门课程时其先修课程已经学过。

拓扑排序的过程

  • 在有向图中找一个无前驱的顶点且输出。

  • 删除该顶点指向另外顶点的弧。

  • 重复以上两步,直至不存在没有前驱的点

  • 若输出的顶点数小于有向图中的顶点数,则说明图中存在环。否则输出的序列即为拓扑排序。
    在这里插入图片描述

    拓扑排序的实现

    需要一个辅助数组indegree[i]来存储各个顶点的入度。

  • 采用邻接矩阵实现

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
#include<iostream>
#include<stdlib.h>
#include<stack>
#include<queue>
using namespace std;

#define MVNum 10//最大顶点数
#define MAXINT 32768//表示极大值

typedef int arcType;
typedef char vertexType;

typedef struct MNode{
vertexType vertex[MVNum]; //存储顶点
arcType Arc[MVNum][MVNum]; //邻接矩阵,存储边
int vexnum,arcnum;
}GraphNode,*Graph;

int indegree[MVNum]={0}; //记录各个顶点的入度

int Init(Graph &G){
G=(GraphNode *)malloc(sizeof(GraphNode));
G->vexnum=0;
G->arcnum=0;
if(G) return 1;
else cout<<"初始化出错!"<<endl;
return 0;
}

void FindPos(Graph G,char a,char b,int &pos1,int &pos2){//查找位置
int i=0;
pos1=-1;pos2=-1;
for(int i=0;i<G->vexnum;i++){
if(G->vertex[i]==a){
pos1=i;
continue;
}
else if(G->vertex[i]==b){
pos2=i;
continue;
}
}
}

void CreateDG(Graph G) //创建有向图
{
int num=0, //控制输入顶点数
pos1,pos2, //确认顶点位置
i,j;
char a,
b,
ch; //顶点
for(int i=0;i<MVNum;i++){ //初始化
for(int j=0;j<MVNum;j++){
G->Arc[i][j]=0;
}
}

cout<<"请输入顶点,以#结束"<<endl;
cin>>ch;
while(ch!='#'&&num<10){
G->vertex[num]=ch;
cin>>ch;
num++;
}
G->vexnum=num;

cout<<"请输入对应的弧(ab与ba是方向相反的边),以##结束"<<endl;
cin>>a>>b;
while(a!='#'&&b!='#'){
cout<<a<<"->"<<b<<endl;
FindPos(G,a,b,pos1,pos2);
cout<<"位置a:"<<pos1<<"位置b:"<<pos2<<endl;
if(pos1!=-1&&pos2!=-1){
G->Arc[pos1][pos2]=1;
G->arcnum++;
indegree[pos2]++;
}
cin>>a>>b;
}
}

void topo(Graph G)
{
stack<int>s;
queue<int>q;
int i,temp;
for(i=0;i<G->vexnum;i++){
if(!indegree[i]) s.push(i);
}

while(!s.empty()){
temp=s.top();
q.push(temp);
s.pop();
for(i=0;i<G->vexnum;i++){
if(G->Arc[temp][i]){
indegree[i]--;
if(indegree[i]==0) s.push(i);
}
}
}

if(q.size()<G->vexnum) printf("该有向图有回路");
else{
while(!q.empty()){
printf("%c ",G->vertex[q.front()]);
q.pop();
}
}
}

void show(Graph G){
for(int i=0;i<G->vexnum;i++){
for(int j=0;j<G->vexnum;j++){
cout<<G->Arc[i][j]<<" ";
}
cout<<endl;
}
}

int main(){
Graph G = NULL;
Init(G);
CreateDG(G);
topo(G);

return 0;
}
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
140
141
142
143
#include <iostream>
#include<stack>
#include<queue>
using namespace std;

#define MVNum 10 //最大顶点信息
int indegree[MVNum]={0};

typedef char VerTexType;
typedef int OtherInfo;

//----------图的邻接表存储表示-----------
typedef struct ArcNode { //边结点
int adjvex; //该边所指向顶点的位置
struct ArcNode *nextarc; //指向下一条边的指针
OtherInfo info; //和边相关的信息

}ArcNode;

typedef struct VNode {
VerTexType data; //顶点信息
ArcNode *fristarrc; //指向第一条依附该顶点的边

} VNode,AdjList[MVNum];

typedef struct {
AdjList vertices; // 邻接表
int vexnum; //图的当前顶点数
int arcnum; //图的当前边数
}ALGraph;

int LocateVex(ALGraph G,VerTexType v) {
for (int i = 0; i < G.vexnum; i++)
if (G.vertices[i].data == v)
return i;
return -1;
}

void CreateDG(ALGraph &G) { //采用邻接表表示法,创建有向图G

int i, k;

cout << "请输入总顶点数和总边数:" ; //输出总顶点数,总边数
cin >> G.vexnum >> G.arcnum;
cout << endl;

cout << "请输入顶点:" << endl;
for (i = 0 ; i < G.vexnum; i++) { //输入各点,构建表头结点
cout << "请输入第" << (i + 1) << "个顶点的名称:";
cin >> G.vertices[i].data;
G.vertices[i].fristarrc = NULL; //初始化表头结点的指针域为NULL
}

cout << endl;
cout << "请输入一条边依附的顶点:" <<endl;
for (k = 0; k < G.arcnum; k++) {
VerTexType v1, v2;
int i, j;
cout << "请输入第" << (k+1) << "条边依附的顶点";
cin >> v1 >> v2; //输入一条边依附的两个顶点
i = LocateVex(G, v1); //确定v1和v2在G中的位置,
j = LocateVex(G, v2); //即顶点在G.vertices(邻接表)中的序号
indegree[j]++;
ArcNode *p1 = new ArcNode; //生成一个新的边结点*p1
p1->adjvex = j; //邻接点序号为j
p1->nextarc = G.vertices[i].fristarrc;
G.vertices[i].fristarrc = p1; //将新节点*p1插入vi的边表

// ArcNode *p2 = new ArcNode; //生成另一个对称的新的边结点*p2
// p2->adjvex = i; //邻接点序号为i
// p2->nextarc = G.vertices[j].fristarrc;
// G.vertices[j].fristarrc = p2; //将新结点*p2插入顶点Vj的边表

}
}

void topo(ALGraph G)
{
stack<int>s;
queue<int>q;

ArcNode *p=NULL;
int i,k,temp;
for(i=0;i<G.vexnum;i++){
if(!indegree[i]) s.push(i);
}

while(!s.empty()){
temp=s.top();
q.push(temp);
s.pop();
p=G.vertices[temp].fristarrc;
while(p!=NULL){
k=p->adjvex;
indegree[k]--;
if(indegree[k]==0) s.push(k);
p=p->nextarc;
}
}

if(q.size()<G.vexnum) printf("该有向图有回路");
else{
while(!q.empty()){
printf("%c ",G.vertices[q.front()].data);
q.pop();
}
}
}

int main(){
ALGraph G;
CreateDG(G);

int i;
cout << endl;
for (i = 0; i < G.vexnum; i++) {
VNode temp = G.vertices[i]; //将G的顶点信息付给temp
ArcNode *p = temp.fristarrc; //将顶点信息temp中的边信息给p
if ( p == NULL){
cout << G.vertices[i].data;
cout << endl;
}
else {
cout << temp.data;
while (p)
{
cout << "->";
cout << p->adjvex;
p = p->nextarc;

}
// if(!p){
// cout<<"->^";
// }
}
cout << endl;
}

topo(G);

return 0;
}

  • 运行结果:

    在这里插入图片描述
    从上面可以看出拓扑排序序列不一定相同,只要满足先序在后序前面即可。
    返回顶部
------------- Thank you for reading -------------

Title - Artist
0:00