拓扑排序原理(本质是图):
用顶点表示活动的网络 (AOV网络)
DFS的逆序就是拓扑排序(邓俊辉mooc有),由0出度的点依次放入栈中,然后栈的输出就是拓扑排序
计划、施工过程、生产流程、程序流程等都是“工程”。除了很小的工程外,一般都把工程分为若干 个叫做“活动”的子工程。完成了这些活动,这个工程就可以完成了。
例如,计算机专业学生的学习就是一个工程,每一 门课程的学习就是整个工程的一些活动。其中有些 课程要求先修课程,有些则不要求。这样在有的课 程之间有领先关系,有的课程可以并行地学习。
可以用有向图表示一个工程。在这种有向图 中,用顶点表示活动,用有向边表示<Vi ,Vj>活动Vi必须先于活动Vj进行。这种有向图叫做 顶点表示活动的AOV网络 。 • 在AOV网络中不能出现有向回路, 即有向环。 如果出现了有向环,则意味着某项活动应以自 己作为先决条件。 • 因此,对给定的AOV网络,必须先判断它是否 存在有向环。
进行拓扑排序的步骤:
① 输入AOV网络。令 n 为顶点个数。
② 在AOV网络中选一个入度为0的结点, 并输出之;(关键步)
③ 从图中删去该顶点, 同时删去所有它发出的有向 边;
④ 重复以上 ②、③步, 直到下面的情况之一出现: (1)全部顶点均已输出,拓扑有序序列形成,拓 扑排序完成; (2)图中还有未输出的顶点, 但已没有入度为0的 结点(说明网络中必存在有向环)。
在邻接表中增设一个数组count[],记录各顶点入度。
入度为零的顶点即无前驱顶点。
在输入数据前, 顶点表NodeTable[]和入度数组count[]全部初始化。
在输入数据时, 每输入一条边<j, k>, 就需要建立一个边结点, 并将它链入相应边链表中, 统计入度信息:
Edge * p = new Edge<int> (k); //建立边结点, dest 域赋为 k
p->link = NodeTable[j].adj;
NodeTable[j].adj = p; //链入顶点j的边链表的前端
count[k]++; //顶点k入度加一
template <class T, class E>
void TopologicalSort (Graph<T, E>& G) {
int i, j, w, v;
int top = -1; //入度为零顶点的栈初始化
int n = G.NumberOfVertices(); //网络中顶点个数
int *count = new int[n]; //入度数组兼入度为零顶点栈
for (i = 0; i < n; i++) count[i] = 0;
cin >> i >> j; //输入一条边(i, j)
while (i > -1 && i < n && j > -1 && j < n) {
G.insertEdge (i, j); count[j]++;
cin >> i >> j;
}
for (i = 0; i < n; i++) //检查网络所有顶点
if (count[i] == 0) //入度为零的顶点进栈
{ count[i] = top; top = i; }
for (i = 0; i < n; i++) //期望输出n个顶点
if (top == -1) { //中途栈空,转出
cout << "网络中有回路!" << endl;
return;
}
else { //继续拓扑排序
v = top; top = count[top]; //退栈v
cout << G.getValue(v) << " " << endl; //输出
w = G.GetFirstNeighbor(v);
while (w != -1) { //扫描顶点v的出边表
count[w]--; //邻接顶点入度减一
if (!count[w]) //入度减至零,进栈
{ count[w] = top; top = w; }
w = G.GetNextNeighbor (v, w);
} //一个顶点输出后,调整其邻接顶点入度
} //输出一个顶点,继续for循环
};
参考博客:
拓扑排序入门(真的很简单)_安得广厦千万间的博客-CSDN博客_拓扑排序
更新
vector实在用于邻接表太棒了,都不用考虑指针
#include<iostream>
#include <vector>
#include <string>
#include <stack>
using namespace std;
const int N = 10010;
int in[N]; //入度辅助数组
vector<int>v[N]; //vector实现有向邻接表
vector<int>p; //保存拓扑节点输出的排序
int main()
{
int n, m;
cout<<"请输入图的顶点个数与边数:";
cin >> n >> m;
memset(in, 0, sizeof(in)); //快速把in[]入度数组全部置0
for (int i = 0; i < n; i++) //把邻接表全部清空数据
v[i].clear(); //可以类比二级指针申请空间去理解
cout<<"请依次输入图每一条边的起点与终点:";
for (int i = 0; i < m; i++)
{
int from, to; // from------>to 有向边
cin >> from >> to;
v[from].push_back(to); //把有向边的终点存入顶点为from的vector中
in[y]++; //此时有向边的终点入度+1
}
stack<int>s; //利用栈保存全部入度为0的顶点
for (int i = 0; i < n; i++)
{
if (!in[i]) //如果入度为0,存入栈中,如果
s.push(i);
}
int node=n;
while (!s.empty())
{
int startnode = s.top(); //startnode为拓扑排序的起点遍历
p.push_back(startnode);//也可以直接输出,不用存进这个vector容器也行
node--; //每遍历一个节点,节点数目减1
s.pop(); //把0入度节点弹出栈
for (int i = 0; i < v[startnode].size(); i++)
{
int nextnode = v[startnode][i]; //逐个遍历以startnode节点的下一个节点
in[nextnode]--; //nextnode节点入度减1,因为已经遍历一次
if (!in[nextnode]) //如果在遍历的过程中发现入度节点为0的点放入栈中
{
s.push(nextnode);
}
}
}
if (!node) //如果node为0,说明图中无环且全部输出(存入了p数组)
{
for (const auto &item : p) cout << item << " ";
cout << endl;
}
else
cout << "错误!该有向图存在环" << endl;
return 0;
}