拓扑排序(Topological Sorting):一种对有向无环图(DAG)的所有顶点进行线性排序的方法,使得图中任意一点 u 和 v,如果存在有向边<u,v>,则 u 必须在 v 之前出现。对有向图进行拓扑排序产生的线性序列称为满足拓扑次序的序列,简称拓扑排序。
图的拓扑排序是针对有向无环图来说的,无向图和有向有环图不存在拓扑排序。
针对以上有向无环图,1→2→3→4→5是该图的一个拓扑序列,1→3→2→4→5也是该图的拓扑序列。1→2→4→3→5不是该图的拓扑序列,因为4依赖于3.对于一个有向无环图来说,拓扑序列可能不止一个。
拓扑排序有两种实现方法,分别是Kahn算法和DFS深度优先搜索算法。
Kahn算法
kahn算法采用入度方法,其算法过程如下:
- 选择一个入度为0的节点(表示该节点不依赖其他节点),输出到结果序列中;
- 删除该节点以及该节点的边;
- 重复执行步骤1和2,直到所有节点输出到结果序列中,完成拓扑排序;
- 如果最后存在入度不为0的节点,说明有向图中存在环,无法进行拓扑排序。
重复以上步骤即可
为了实现这个算法,使用邻接表来表示有向图,并且需要一个数组来存储每个结点的入度(指向该结点的结点数量),如果为 0 则说明该结点没有结点指向它。遍历所有节点,将入度为0的点即节点1入栈
弹出栈顶元素即节点1并输出,然后遍历节点1的邻接节点(节点2,3),将它们的入度减1,如果入度为0则入栈,重复此操作,直到栈为空,算法结束。
#include <bits/stdc++.h>
using namespace std;
#define MAXVERTIES 20
#define OK 1
#define ERROR 0
int indegree[MAXVERTIES] = { 0 }; //用于存储入度信息
//定义结点
struct VexNode {
int data;
VexNode *next;
};
//定义弧
struct ArcNode {
int data;
VexNode *firstacr = NULL;
};
//定义邻接表
struct GraphList {
ArcNode arclist[MAXVERTIES];
int vexnum, arcnum;
};
//定义栈
struct Stack {
int Sacklist[MAXVERTIES] = { 0 };
int top = -1;
};
//入栈操作
void Push(Stack &S, int key) {
if (S.top == MAXVERTIES) {
cout << "栈已满!" << endl;
return;
}
S.top++;
S.Sacklist[S.top] = key;
}
//出栈操作
int Pop(Stack &S) {
if (S.top == -1) {
cout << "栈为空!" << endl;
return -1;
}
int temp = S.Sacklist[S.top];
S.top--;
return temp;
}
//返回结点在结点数组中的下标
int Location(GraphList &G, int key) {
for (int i = 0; i < G.vexnum; i++) {
if (G.arclist[i].data == key) {
return i;
}
}
return -1;
}
//创建图
void CreatGraph(GraphList &G) {
cout << "请输入顶点数:" << endl;
cin >> G.vexnum;
cout << "请输入顶点信息:" << endl;
for (int i = 0; i < G.vexnum; i++) {
cin >> G.arclist[i].data;
}
cout << "请输入弧数:" << endl;
cin >> G.arcnum;
cout << "请输入弧端点信息:" << endl;
for (int i = 0; i < G.arcnum; i++) {
int v1, v2;
cin >> v1 >> v2;
int Location1 = Location(G, v1);
int Location2 = Location(G, v2);
VexNode *new_node = new VexNode;
new_node->data = Location2;
new_node->next = G.arclist[Location1].firstacr;
G.arclist[Location1].firstacr = new_node;
indegree[Location2]++;
}
}
//拓扑排序
int TopoSort(GraphList &G, int *topolist) {
Stack S;
int topo = 0;
//先将所有入度为0的结点入栈
for (int i = 0; i < G.vexnum; i++) {
if (indegree[i] == 0) {
Push(S, i);
}
}
//依次出栈入度为0的结点
while (S.top != -1) {
int vx = Pop(S);
topolist[topo++] = G.arclist[vx].data; //输出结点
VexNode *temp = G.arclist[vx].firstacr;
//删除以该结点为尾的弧
while (temp != NULL) {
int index = temp->data;
indegree[index]--; //将该弧的弧头结点入度减1
//如果入度为0,则入栈
if (indegree[index] == 0) {
Push(S, index);
}
temp = temp->next;
}
}
topolist[topo] = -1;
//如果拓扑序列中的元素个数等于所有元素个数,则该图无环,否则该图有环
if (topo == G.vexnum) {
return OK;
} else {
return ERROR;
}
}
int main() {
GraphList GL;
CreatGraph(GL);
int topolist[MAXVERTIES] = { 0 };
int vx = TopoSort(GL, topolist);
if (!vx) {
cout << "有环!" << endl;
} else {
cout << "有向无环!" << endl;
cout << "拓扑序列如下:" << endl;
for (int i = 0; topolist[i] != -1; i++) {
cout << topolist[i] << " ";
}
}
return 0;
}
DFS
DFS算法采用出度算法,其算法过程如下:
- 对有向图进行深度优先搜索;
- 在执行深度优先搜索时,若某个顶点不能继续前进,即顶点的出度为0,则将此顶点入栈。
- 最后对栈中的序列进行逆排序,即完成拓扑排序;如果深度优先搜索时,碰到已遍历的节点,说明存在环。