介绍
拓扑排序是一种重要的图算法,主要用于解决有向无环图(Directed Acyclic Graph,简称DAG)中的节点排序问题。它是将一个有向无环图中的所有顶点排成一个线性序列,使得对于图中的任意一对顶点u和v,若存在一条从u到v的有向边,则u在线性序列中一定出现在v之前。这样的线性序列称为满足拓扑次序的序列,简称拓扑序列。
大体思路
- 构造队列和结果队列:首先,构造一个队列Q用于存放没有依赖顶点的节点,以及一个结果队列T用于存放拓扑排序的结果。
- 选取无依赖顶点:将所有没有依赖顶点的节点(即入度为0的节点)放入队列Q中。
- 进行排序:当队列Q中还有顶点时,执行以下步骤:
- 从Q中取出一个顶点n,并将其放入结果队列T中。
- 对于顶点n的每一个邻接点m(即存在从n到m的有向边),执行以下操作:
- 去掉边<n, m>。
- 如果m没有依赖顶点(即m的入度减1后为0),则将m放入队列Q中。
- 检查是否存在环路:如果图中存在环路,则无法进行拓扑排序,因为环路上的节点无法确定一个合理的排序顺序。
实现步骤
1.0结构体等定义
本次实现用的的结构是邻接表,所以定义了其结构。
indegree数组用来记录某个顶点的入度;print数组用来记录图的实现顺序。
2.0 核心算法
具体步骤
1.0开始时,先遍历邻接表,记录各个顶点的入度。
2.0创建stack。 然后将记录的入度为零的点压入栈中。
3.0接下来,循环判断栈非空,那就弹出栈顶元素,并将其记录在print数组中。紧接着将其相邻的顶点的入度都减少1(因为我们栈顶元素代表的顶点已经实现过了,逻辑上要删除与其相邻的顶点之间的边),同时把相邻的度为零的顶点压入栈中。
4.0循环结束后,判断操作次数与顶点个数是否相同。如果相同则拓扑排序成功,如果不相同,说明有环等原因导致不成功。
3.0举例验证
如图所举实例,那么最后的顺序应该是0-1-2-3-4/0-2-1-3-1/2-0-1-3-4.我们运行判断发现满足2-0-1-3-4情况。
源代码
#include<iostream> #include<vector> #include<stack> class ALGraph{ private: int V;//顶点数量 struct Edge{ int target;//顶点索引 int weight;//权重 Edge*next;// }; std::vector<Edge*>adj; std::vector<int>indegree; std::vector<int>print; public: ALGraph(int V):V(V),adj(V,nullptr),indegree(V),print(V,-1){} void printff(){ for(int i = 0;i<V;i++){ std::cout << print[i]<<"-->"; } } void AddEdge(int a,int b,int weight = 0){ Edge*edge = new Edge{b,weight,adj[a]}; adj[a] = edge; } bool Topologicalsort(){ //求出入度数组 for(int i = 0;i<V;i++){ for(Edge*p = adj[i];p!=nullptr;p = p->next){ indegree[p->target] ++; } } //创建栈stack std::stack<int>S; for(int i = 0;i<V;i++){ if(!indegree[i]){ S.push(i); } } //用于计数 int count = 0; while(!S.empty()){ //记录当前顶点信息 int current = S.top(); S.pop(); print[count++] = current; for(Edge*p = adj[current];p!=nullptr;p = p->next){ int v = p->target; if(!(--indegree[v])){ S.push(v); } } }//while if(count<V){ //此时说明有环 return false; } return true; } }; int main() { ALGraph g(5); g.AddEdge(0,1); g.AddEdge(1,3); g.AddEdge(2,3); g.AddEdge(2,4); g.AddEdge(3,4); if(g.Topologicalsort()){ std::cout << "success!"<<std::endl; g.printff(); } return 0; }