何为拓扑排序:
一般常见的图是有环的,不需要考虑顶点的先后顺序,而比如在一些工程中,工程的流程图就需要考虑顶点的先后顺序,比如B的前置条件是完成A,这就需要拓扑排序来判断图是否满足拓扑次序,并且能够形象的显示各个顶点的先后顺序。
拓扑排序有多种解,拓扑排序的解并不是唯一的,有些顶点的优先性是相同的。
通常我们把顶点表示活动,边表示活动的先后关系的有向图称为顶点活动网(Activity On Vertex Network),即AOV网。
拓扑排序的算法思路:
有向图的每个顶点有不同的入度,不断把入度为0的顶点的去除,并去除与之相连的边,把其相连顶点的入度减小1,不断循环。
最终判断是否输出的顶点数小于原图的顶点数,小于则不满足拓扑次序,否则满足拓扑次序,输出的顶点为一种拓扑序列。
拓扑排序所需的图的邻接表结构:
为了方便去除入度为0的顶点和边,所以使用带入度的图的邻接结构,图的邻接表结构如下:
#define MAXVERTEX 10 //最大顶点数,默认为10
struct EdgeNode //边表
{
int adjvertex; //邻接点的下标
int weight; //权值
EdgeNode *next; //指针指向下一个邻接点
};
typedef struct VertexNode //顶点表
{
int data; //顶点信息
int in; //顶点入度
EdgeNode *firstEdge; //边表指针,指向边表
}AdjList[MAXVERTEX];
typedef struct GraphAdjList
{
AdjList adjList;
int vertexesNum; //顶点数
int edgesNum; //边数
}*GraphAdjList;
拓扑排序实现:
辅助建立了一个栈来输出入度为0的顶点
//拓扑排序
bool TopologicalSort(GraphAdjList GAL)
{
int i;//用于循环
int count; //存储要输出的顶点个数
EdgeNode *e;
int k;
//初始化用于辅助的栈
stack<int> stack; //栈用于存放入度为0的顶点
int topNum = 0; //存储取出的栈顶数
//将入度为0的顶点放入栈
for (i = 0; i < GAL->vertexesNum; i++)
{
if (GAL->adjList[i].in == 0)
{
stack.push(i);
}
}
while (stack.empty() != true) //如果存在入度为0的顶点,不断循环
{
topNum = stack.top(); //topNum赋值为栈顶指针
stack.pop(); //栈顶出栈
count++; //输出个数自增1
cout << GAL->adjList[topNum].data << endl; //输出顶点
//遍历该顶点的邻接表
for (e = GAL->adjList[topNum].firstEdge; e; e = e->next)
{
k = e->adjvertex;
if ((--GAL->adjList[k].in) == 0) //邻接点自减1,并判断是否为0
{
stack.push(k);
}
}
}
//循环结束,判断是否是拓扑次序
if (count<GAL->vertexesNum)
{
return false;
}
else
{
return true;
}
}
拓扑排序时间复杂度:
初始遍历入度为0的顶点复杂度为O(n),后来循环中入栈出栈,复杂度为O(e),总的时间复杂度O(n+e)。