拓扑排序是图G中所有节点的一种线性次序,该次序满足以下条件:如果图G包含边(u,v),则节点u在拓扑排序中处于节点v的前面。
在实际生活中有很多应用需要用有向无环图来指明事件的优先次序。下面以一个穿衣的例子来讲解拓扑排序:
在穿每一件衣服之间,要考虑是否要求先穿上某件衣服。拓扑排序可以解决这个问题,算法的主要思想如下:
通过深度优先搜索,记录每个节点的开始扫描的时间d和将其所有子节点全部扫描完的时间f,按照f从大到小的顺序,从左向右排列,得到的即为拓扑排序。在具体代码操作中,由于先扫描完成的节点总是f最小的,所以将每个扫描完的节点添加到数组中,再按照反序输出即可。
具体代码如下:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Scanner;
/**
*
* @author Founder
* 作者原创,转载请注明出处
*/
public class Main{
static int time = 0;
static ArrayList<Integer> topology;
public static void main(String[] args){
/**
* 输入方式:
* 第一行输入节点的个数n
* 后面n行输入第n个节点(从0开始数)链接的子节点,没有子节点则直接换行
*/
Scanner input = new Scanner(System.in);
int n = input.nextInt();
Node[] nodes = new Node[n];
topology = new ArrayList<>();
for(int i = 0; i < n; ++i){
nodes[i] = new Node();
}
input.nextLine();
for(int i = 0; i < n; ++i){
String line = input.nextLine();
LinkedList<Integer> linkNodes = new LinkedList<>();
if(!line.equals("")){
String[] tempIntStr = line.split(" ");
for(int j = 0; j < tempIntStr.length; ++j){
linkNodes.add(Integer.parseInt(tempIntStr[j]));
}
}
nodes[i].setLinkNodes(linkNodes);
}
dfs(nodes);
for(int i = topology.size() - 1; i >= 0; --i){
System.out.print(topology.get(i) + " ");
}
}
public static void dfs(Node[] nodes){
for(int i = 0; i < nodes.length; ++i){
if(nodes[i].getColor() == Node.WHITE)
dfsVisit(nodes,i);
}
}
public static void dfsVisit(Node[] nodes,int no){
time++;
nodes[no].setColor(Node.GRAY);
nodes[no].setD(time);
LinkedList<Integer> linkNodes = nodes[no].getLinkNodes();
for(int i = 0; i < linkNodes.size(); ++i){
Node temp = nodes[linkNodes.get(i)];
if(temp.getColor() == Node.WHITE){
temp.setParent(nodes[no]);
dfsVisit(nodes,linkNodes.get(i));
}
}
nodes[no].setColor(Node.BLACK);
topology.add(no);
time++;
nodes[no].setF(time);
}
}
class Node{
public static final int WHITE = 0;
public static final int GRAY = 1;
public static final int BLACK = 2;
private int color = WHITE;
private int d = 0;
private int f = 0;
private Node parent = null;
private LinkedList<Integer> linkNodes = null;
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getD() {
return d;
}
public void setD(int d) {
this.d = d;
}
public int getF() {
return f;
}
public void setF(int f) {
this.f = f;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public LinkedList<Integer> getLinkNodes() {
return linkNodes;
}
public void setLinkNodes(LinkedList<Integer> linkNodes) {
this.linkNodes = linkNodes;
}
@Override
public String toString() {
return "Node [d=" + d + ", f=" + f + "]";
}
}
对于本例,输入如下:
9
1 7
2 7
5
2 4
5
(没有输入,直接换行)
7
(没有输入,直接换行)
(没有输入,直接换行)
输出:
8 6 3 4 0 1 7 2 5
即按照如下顺序进行:
符合题目要求。
基本原理理解:
深度优先搜索森林中,每个树里f最小的节点,没有子节点,因此放在该树的最右边,在回溯时,将其父节点放在左边,使每个树符合拓扑排序的要求;森林中不同树之间不干扰,因此不同树的放置顺序不影响拓扑排序,所以整个排序符合拓扑排序要求。