拓扑排序:
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
执行步骤
由AOV网构造拓扑序列的拓扑排序算法主要是循环执行以下两步,直到不存在入度为0的顶点为止。
(1) 选择一个入度为0的顶点并输出之;
(2) 从网中删除此顶点及所有出边。
循环结束后,若输出的顶点数小于网中的顶点数,则输出“有回路”信息,否则输出的顶点序列就是一种拓扑序列。
package com.gloomy.graph;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;
/**
* 拓扑排序 基于DFS的算法,加入结果集的条件是:顶点的出度为0。 基于Kahn算法,加入结果集的条件是:顶点入度为0。
* 两种算法的思想犹如一枚硬币的两面,看似矛盾,实则不然。一个是从入度的角度来构造结果集,另一个则是从出度的角度来构造。
*
* @author 过路的守望
*
*/
public class ToplogicalSort {
/**
* 顶点类
*
* @author 过路的守望
*
*/
private static class Vertex {
/*
* @indgree 入度
*/
private int indgree;
/*
* @label 标签
*/
private String label;
/*
* 前继顶点
*/
private Vertex preVertex;
/*
* 记录是否访问过
*/
private boolean visited;
/*
* 记录是否在栈中
*/
private boolean inStack;
/*
* @adjacentlist 邻接表
*/
private List<Vertex> adjacentList;
public Vertex(int indgree, String label) {
this.adjacentList = new ArrayList<Vertex>();
this.indgree = indgree;
this.label = label;
}
public List<Vertex> getAdjacentList() {
return adjacentList;
}
public int getIndgree() {
return indgree;
}
public String getLabel() {
return label;
}
public Vertex getPreVertex() {
return preVertex;
}
public boolean isInStack() {
return inStack;
}
public boolean isVisited() {
return visited;
}
public void setAdjacentList(List<Vertex> adjacentList) {
this.adjacentList = adjacentList;
}
public void setIndgree(int indgree) {
this.indgree = indgree;
}
public void setInStack(boolean inStack) {
this.inStack = inStack;
}
public void setLabel(String label) {
this.label = label;
}
public void setPreVertex(Vertex preVertex) {
this.preVertex = preVertex;
}
public void setVisited(boolean visited) {
this.visited = visited;
}
}
public static void main(String[] args) {
Vertex A = new Vertex(0, "A");
Vertex B = new Vertex(1, "B");
Vertex C = new Vertex(2, "C");
Vertex D = new Vertex(3, "D");
Vertex E = new Vertex(1, "E");
Vertex F = new Vertex(3, "F");
Vertex G = new Vertex(2, "G");
A.getAdjacentList().add(B);
A.getAdjacentList().add(C);
A.getAdjacentList().add(D);
B.getAdjacentList().add(D);
B.getAdjacentList().add(E);
C.getAdjacentList().add(F);
D.getAdjacentList().add(C);
D.getAdjacentList().add(F);
D.getAdjacentList().add(G);
E.getAdjacentList().add(D);
E.getAdjacentList().add(G);
G.getAdjacentList().add(F);
ToplogicalSort sort = new ToplogicalSort();
List<Vertex> vertexs = new ArrayList<Vertex>();
vertexs.add(A);
vertexs.add(B);
vertexs.add(C);
vertexs.add(D);
vertexs.add(E);
vertexs.add(F);
vertexs.add(G);
/* List<Vertex> pathList = sort.toplogicalSort(vertexs); */
List<Vertex> pathList = sort.toplogicalSortByRec(vertexs);
Collections.reverse(pathList);
if (pathList != null) {
for (Vertex v : pathList) {
System.out.print(v.getLabel() + "-->");
}
}
}
/*
* 记录拓扑排序的路径
*/
private List<Vertex> rootList = new ArrayList<Vertex>();
/*
* 添加入度为0的顶点
*/
private Queue<Vertex> queue;
/*
* 添加环中的顶点
*/
private Stack<Vertex> stack;
/**
* 判断图是DAG可以使用基于DFS的算法,复杂度为O(E+V),而后面的拓扑排序也是依赖于DFS,复杂度为O(E+V)
* 因为添加顶点到集合中的时机是在DFS方法即将退出之时
* ,而DFS方法本身是个递归方法,只要当前顶点还存在边指向其它任何顶点,它就会递归调用DFS方法,
* 而不会退出。因此,退出DFS方法,意味着当前顶点没有指向其它顶点的边了,即当前顶点是一条路径上的最后一个顶点。
*
* @param vertex
* @return
*/
private List<Vertex> DFSToplogicalSort(Vertex vertex) {
/*
* 当前顶点已被访问
*/
vertex.setVisited(true);
/*
* 当前顶点在栈中,用来检测是否含环
*/
vertex.setInStack(true);
for (Vertex vertex2 : vertex.getAdjacentList()) {
if (hasCycle()) {
return null;
}
if (!vertex2.isVisited()) {
/*
* 记录前继顶点
*/
vertex2.setPreVertex(vertex);
DFSToplogicalSort(vertex2);
}
/*
* 顶点已被访问过并且在栈中,现在再次被访问,证明存在环
*/
else if (vertex2.isInStack()) {
stack = new Stack<ToplogicalSort.Vertex>();
/*
* stack记录环的组成顶点
*/
stack.push(vertex2);
for (Vertex begin = vertex; begin != vertex2; begin = begin
.getPreVertex()) {
stack.push(begin);
}
stack.push(vertex2);
}
}
/*
* 当前顶点的邻接顶点全部处理完后将当前顶点加入rootList中
*/
rootList.add(vertex);
/*
* 标记当前顶点不在栈中
*/
vertex.setInStack(false);
return rootList;
}
/*
* 将所有入度为0的顶点放入一个初始为空的队列中
*/
public void findNewVertexOfIndgreeZeros(List<Vertex> vertexs) {
Iterator<Vertex> it = vertexs.iterator();
while (it.hasNext()) {
Vertex curVertex = it.next();
/*
* 遍历集合时若要删除集合中的元素用迭代器防止抛出异常
*/
if (curVertex.getIndgree() == 0) {
queue.offer(curVertex);
it.remove();
}
}
return;
}
/*
* 判断有无环路
*/
public boolean hasCycle() {
return null != stack;
}
/*
* 拓扑排序顺序不一定是唯一的
*/
public List<Vertex> toplogicalSort(List<Vertex> vertexs) {
rootList = new ArrayList<Vertex>();
/*
* 使用一个队列可以不为Vertex设置是否访问了的属性。因为遍历时一个元素从队列中删除后不会再次加入进队列
*/
queue = new LinkedList<Vertex>();
/*
* 将所有入度为0的顶点放入一个初始为空的队列中
*/
findNewVertexOfIndgreeZeros(vertexs);
Vertex curVertex = null;
while (!queue.isEmpty()) {
/*
* 当队列不为空时,删除一个顶点v,并将与v邻接的所有顶点入度均减一。
* 只要有一个顶点的入度为0,就把该顶点放入队列中。此时,拓扑排序的顺序就是 顶点出队的顺序。
*/
curVertex = queue.poll();
vertexs.remove(curVertex);
rootList.add(curVertex);
for (Vertex adjacentVertex : curVertex.getAdjacentList()) {
adjacentVertex.setIndgree(adjacentVertex.getIndgree() - 1);
/*
* 将入度为0的顶点加入到队列
*/
if (adjacentVertex.getIndgree() == 0) {
queue.offer(adjacentVertex);
}
}
}
/*
* 如果存在环,则vertexs中的顶点未清除完
*/
if (!vertexs.isEmpty()) {
System.out.println("存在环!");
return null;
}
return rootList;
}
/**
* 基于深度优先遍历递归实现拓扑排序
*
* @param vertexs
* @return
*/
public List<Vertex> toplogicalSortByRec(List<Vertex> vertexs) {
for (Vertex vertex : vertexs) {
if (!vertex.isVisited()) {
DFSToplogicalSort(vertex);
}
}
return rootList;
}
}