简介:
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。如下图所示。
排序过程:
观察上图可以发现,每一个顶点都不会通过边指向自己的前驱顶点。
- 在有向图中,选取一个无前驱顶点且输出它
- 删除此顶点以及该顶点与其他顶点的连接
- 重复1,2步骤,直至不存在无前驱的顶点
算法实现:
为了不破坏图之间的关系,我们使用一个数组记录每个顶点的入度(入度表明顶点的前驱个数)。通过栈记录无前驱的顶点,倘若栈无记录则不存在无前驱的顶点。这里使用栈的原因是为了避免重复扫描记录入度的数组,因此使用队列同样可以达到目的。如果想记录拓扑排序的顶点序号,则需再增设一个数组。
实现步骤:
- 首先获取各顶点入度信息
- 若存在无前驱(入度=0)的顶点,则入栈
- 若栈不为空,则删除当前栈顶元素与其邻接点之间的连接,更新记录入度信息的数组
- 若存在入度为0的顶点,则入栈
代码示例:
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
#define MAX_SIZE 100
int graph[MAX_SIZE][MAX_SIZE];
int degree[MAX_SIZE];
int seq[MAX_SIZE];
void init_arry(){
for(int i = 0; i < MAX_SIZE; i++){
for(int j = 0; j < MAX_SIZE; j++)
graph[i][j] = 0;
}
}
void init_graph(int m){
for(int i = 0; i < m; i++){
int x, y;
cin >> x >> y;
graph[x][y] = 1;
}
}
void get_degree(int n){
for(int i = 1; i <= n; i++){
int sum = 0;
for(int j = 1; j <= n; j++){
sum += graph[j][i];
}
degree[i] = sum;
}
}
bool topology_sort(int n){
//stack<int> s;
queue<int> s;
int count = 0;
for(int i = 1; i <= n; i++){
if(degree[i] == 0)
s.push(i);
}
while(!s.empty()){
//int t = s.top();栈
//s.pop();
int t = s.front();//队列
for(int i = 1; i <= n; i++){
if(graph[t][i] != 0){
degree[i]--;
if(degree[i] == 0)
s.push(i);
}
}
seq[count++] = t;
s.pop();
}
if(count == n) return true;
else return false;
}
int main(){
int n, m;
cin >> n >> m;
init_arry();
init_graph(m);
get_degree(n);
if(topology_sort(n)){
for(int i = 0; i < n; i++)
cout << seq[i] << " ";
}
return 0;
}
也可以通过记录拓扑序列的数组查看图中是否有环(AOV-网是有向无环图)。排序过程中只有无前驱的节点才会被记录,若存在环,则不会被记录。因此最终比较数组和顶点的大小便可确定是否存在环。
此外,通过打印记录数组可以发现,使用栈和队列的记录顺序是不同的。正确性毋庸置疑,因此可以得出拓扑排序序列不唯一的结论。