拓扑排序主要解决一个工程能否顺序进行的问题。
无环图:图中没有回路
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称为AOV网(Activity On Vertex Network)。
AOV网中的弧表示活动之间存在的某种制约关系(也可以理解活动要分先后顺序),AOV网中不能有回路。
设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2,…vn,满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。则我们称这样的顶点序列为一个拓扑序列。
拓扑排序:就是对一个有向图构造拓扑序列的过程。
构造时会有两个结果,如果此网的全部顶点都被输出,则说明它是不存在环(回路)的AOV网;如果输出顶点数少了,哪怕少了一个,也说明这个网存在环(回路),不是AOV网。
一个不存在回路的AOV网,我们可以将它应用在各种各样的工程或项目的流程图中,满足各种应用场景的需要,所以实现拓扑排序的算法就很有价值了。
拓扑排序算法:
对AOV网进行拓扑排序的基本思路是:从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点为止。
C# 代码:可以只看最后的拓扑排序部分
//测试数据:就是上面的图中的数据,可以把VertexNode类放到外面,就不用嵌套写了,也可以用简化语法using之类的
AdjacencyList<int>.VertexNode<int>[] vertexArr = new AdjacencyList<int>.VertexNode<int>[] {
new AdjacencyList<int>.VertexNode<int>() { data = 0,inDegree=0, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=11,next=new AdjacencyList<int>.EdgeNode(){adjvex=5,next=new AdjacencyList<int>.EdgeNode(){ adjvex=4} } } },
new AdjacencyList<int>.VertexNode<int>() { data = 1,inDegree=0, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=8,next=new AdjacencyList<int>.EdgeNode(){adjvex=4,next=new AdjacencyList<int>.EdgeNode(){ adjvex=2} } } },
new AdjacencyList<int>.VertexNode<int>() { data = 2,inDegree=2, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=9,next=new AdjacencyList<int>.EdgeNode(){adjvex=6,next=new AdjacencyList<int>.EdgeNode(){ adjvex=5} } } },
new AdjacencyList<int>.VertexNode<int>() { data = 3,inDegree=0, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=13,next=new AdjacencyList<int>.EdgeNode(){adjvex=2 } } },
new AdjacencyList<int>.VertexNode<int>() { data = 4,inDegree=2, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=7} },
new AdjacencyList<int>.VertexNode<int>() { data = 5,inDegree=3, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=12,next=new AdjacencyList<int>.EdgeNode(){adjvex=8 } } },
new AdjacencyList<int>.VertexNode<int>() { data = 6,inDegree=1, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=5} },
new AdjacencyList<int>.VertexNode<int>() { data = 7,inDegree=2},
new AdjacencyList<int>.VertexNode<int>() { data = 8,inDegree=2, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=7} },
new AdjacencyList<int>.VertexNode<int>() { data = 9,inDegree=1, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=11,next=new AdjacencyList<int>.EdgeNode(){adjvex=10 } } },
new AdjacencyList<int>.VertexNode<int>() { data = 10,inDegree=1, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=13} },
new AdjacencyList<int>.VertexNode<int>() { data = 11,inDegree=2},
new AdjacencyList<int>.VertexNode<int>() { data = 12,inDegree=1, firstEdge=new AdjacencyList<int>.EdgeNode(){ adjvex=9} },
new AdjacencyList<int>.VertexNode<int>() { data = 13,inDegree=2},
};
AdjacencyList<int> al = new AdjacencyList<int>(vertexArr);
al.TopologicalSort();
//输出结果:->3->1->2->6->9->10->13->0->4->5->8->7->12->11
//序列可能不止一个,只要输出了所有结点就是对的。
源码:拓扑排序在最后
/// <summary>
/// 泛型邻接表:一维数组 + 链表(数组和链表自行提供)
/// </summary>
public class AdjacencyList<T>
{
/// <summary>
/// 边表结点
/// </summary>
public class EdgeNode
{
public int adjvex; //邻接点域,存储该顶点对应的下标
public int weight; //用于存储权值,对于非网图可以不需要(关键路径需要权值)
public EdgeNode next; //链域,指向下一个邻接点
}
/// <summary>
/// 顶点表结点
/// </summary>
public class VertexNode<T>
{
public int inDegree; //入度:拓扑排序用
public T data; //顶点域,存储顶点信息
public EdgeNode firstEdge; //边表头指针
}
private VertexNode<T>[] adjacencyArr; //顶点表
/// <summary>
/// 说明:没有按书上写,需要的数据自行提供
/// </summary>
/// <param name="vertexData">顶点数据</param>
public AdjacencyList(VertexNode<T>[] vertexData)
{
adjacencyArr = vertexData;
}
private bool[] visited;
#region 操作
/// <summary>
/// 邻接表的深度优先遍历
/// </summary>
public void DFSTraverse()
{
visited = new bool[adjacencyArr.Length];
for (int i = 0; i < adjacencyArr.Length; i++)
{
visited[i] = false; //初始化所有顶点都是未访问的状态
}
for (int i = 0; i < adjacencyArr.Length; i++)
{
//对未访问过的顶点调用DFS,若是连通图,只会执行一次
if (!visited[i])
{
DFS(i);
}
}
}
/// <summary>
/// 邻接表的深度优先递归算法
/// </summary>
/// <param name="gl"></param>
/// <param name="i"></param>
private void DFS(int i)
{
visited[i] = true;
//这里对顶点的操作,这里简单的打印
Debug.Log(adjacencyArr[i].data);
EdgeNode temp = adjacencyArr[i].firstEdge;
while (temp != null)
{
if (!visited[temp.adjvex])
DFS(temp.adjvex);
temp = temp.next;
}
}
/// <summary>
/// 邻接表:广度优先遍历
/// </summary>
/// <param name="gl"></param>
public void BFSTraverse()
{
visited = new bool[adjacencyArr.Length];
Queue<int> queue = new Queue<int>(); //初始化辅助队列
EdgeNode p;
for (int i = 0; i < adjacencyArr.Length; i++)
{
visited[i] = false;
}
for (int i = 0; i < adjacencyArr.Length; i++) //对每一个顶点做循环
{
if (!visited[i]) //若是未访问过就处理
{
visited[i] = true; //设置当前顶点访问过
//这里对顶点的操作,这里简单的打印
Debug.Log(adjacencyArr[i].data);
queue.Enqueue(i); //将此顶点入队列
while (queue.Count > 0) //当前队列有元素
{
i = queue.Dequeue(); //出队列
p = adjacencyArr[i].firstEdge; //找到当前顶点边表链表头指针
while (p != null)
{
//此顶点未访问过
if (!visited[p.adjvex])
{
visited[p.adjvex] = true;
//这里对顶点的操作,这里简单的打印
Debug.Log(adjacencyArr[p.adjvex].data);
queue.Enqueue(p.adjvex); //将此顶点入队列
}
p = p.next; //指针指向下一个邻接点
}
}
}
}
}
/// <summary>
/// 拓扑排序:需要一个栈辅助,存储处理过程中入度为0的顶点
/// 目的是为了避免么个查找时都要去遍历顶点表找有没有入度为0的顶点
/// </summary>
/// <returns>无回路返回true,有回路返回false</returns>
public bool TopologicalSort()
{
EdgeNode node;
int k, topIndex;
int count = 0; //用于统计输出顶点的个数
Stack<int> stack = new Stack<int>(adjacencyArr.Length); //建栈存储入度为0的顶点
for (int i = 0; i < adjacencyArr.Length; i++)
{
if (adjacencyArr[i].inDegree == 0)
{
stack.Push(i);
}
}
string result = string.Empty;
while (stack.Count != 0)
{
topIndex = stack.Pop(); //出栈
result += "->" + adjacencyArr[topIndex].data; //打印此顶点
count++; //统计输出顶点数
for (node = adjacencyArr[topIndex].firstEdge; node != null; node = node.next)
{
//对此顶点弧表遍历
k = node.adjvex;
if ((--adjacencyArr[k].inDegree) == 0) //将k号顶点邻接点的入度减1
{
stack.Push(k); //若为0则入栈,以便下次循环输出
}
}
}
Debug.Log(result); //打印结果
if (count < adjacencyArr.Length) return false; //如果count小于顶点数,说明存在环
else return true;
}
#endregion
}