拓扑排序示例
在图论中,一个有向无环图的所有顶点可以排列成一个现行序列,当这个序列满足以下条件时,称该序列为一个满足图的拓扑次序(topological order)的序列。
1) 图中每个顶点在序列中只出现一次;
2) 对于图中任意一条有向边(u,v),在该序列中顶点u一定在顶点v之前。
这样的序列也被称为拓扑序列,对有向图的所有顶点排序,获得拓扑序列的过程就是有向图的拓扑排序。
1、 拓扑排序的基本过程:
1) 从有向图中选择一个没有前驱(入度为0)的顶点,输出这个顶点;
2) 从有向图中删除该顶点,同时删除由该顶点发出的所有有向边。
2、 按照活动(最早)开始时间排序
在一个工程中,每个活动的开始时间受前置活动的约束,不可能随时开始。但是活动的最早开始可以根据前置活动之间的关系推算出来,具体推算的方法如下。
1) 如果一个活动没有前置活动,则这个活动的开始时间是0
2) 如果一个活动有前驱活动,则这个活动的最早开始时间是前驱活动的最早开始时间加上前驱活动的持续时间,如果一个活动有多个前驱活动,则选取和最大的一个。
按照最早开始时间进行排序并不是拓扑排序的必须任务,算是对拓扑排序的优化,当有多个入度为0的活动时,选择开始时间最早的活动进行。在实现时使用java的PriorityQueue数据结构,即优先级队列。优先级队列中存放的是活动(Activity),需要自定义比较器,使之按照最早开始时间排序。
本实验使用的有向图如下,在实验前首先使用init()方法生成邻接表,同时初始化持续时间和最早开始时间,本实验假设最早开始时间已知:
活动的定义为:
package ts;
import java.util.ArrayList;
import java.util.List;
public class Activity {
String name;
int days; //持续时间
int sTime; //最早开始时间
int inCnt; //入度
List<Activity> adjs = new ArrayList<Activity>();
/**
* 构造函数
* @param name 活动名称
* @param days 活动持续实际
* @param sTime 活动最早开始执行时间
*/
public Activity(String name,int days,int sTime){
this.name = name;
this.days = days;
this.sTime = sTime;
}
/**
* 添加一个依赖边
* @param adj 有向边的另一端
* @return 活动自身,为了实现链式add操作
*/
public Activity addAdj(Activity adj){
adjs.add(adj);
adj.inCnt++;
return this;
}
@Override
public String toString() {
return "Activity [name=" + name + ", days=" + days + ", sTime=" + sTime + ", inCnt=" + inCnt + "]";
}
}
用于优先级队列的活动比较器定义为:
package ts;
import java.util.Comparator;
/**
*按照最早开始时间排序的活动(Activity)比较器
* @author dem
*/
public class ActiComparator implements Comparator<Activity> {
@Override
public int compare(Activity arg0, Activity arg1) {
return arg0.sTime-arg1.sTime;
}
}
主运行函数定义为:
package ts;
import java.util.ArrayList;
import java.util.PriorityQueue;
/**
* 拓扑排序,输入为图的邻接表(已知入度),输出为排好的节点顺序
* 将所有入度为0的节点加入一个优先级队列,按照最早开始时间排序,
* 最早开始时间的活动最先被执行
* @author dem
*/
public class TS {
//生成一个初始图,用邻接表(即一个数组)表示,初始化入度和最早开始时间
public Activity[] init(){
Activity[] as = new Activity[10]; //下标0不用
as[1] = new Activity("p1",8,0);
as[2] = new Activity("p2",5,0);
as[3] = new Activity("p3",6,8);
as[4] = new Activity("p4",4,14);
as[5] = new Activity("p5",7,5);
as[6] = new Activity("p6",7,18);
as[7] = new Activity("p7",4,8);
as[8] = new Activity("p8",3,12);
as[9] = new Activity("p9",4,18);
as[1].addAdj(as[3]).addAdj(as[7]);
as[2].addAdj(as[3]).addAdj(as[5]);
as[3].addAdj(as[4]);
as[4].addAdj(as[6]).addAdj(as[9]);
as[5].addAdj(as[6]);
as[7].addAdj(as[8]);
as[8].addAdj(as[9]);
return as;
}
/**
* 拓扑排序
* @param as 邻接表
* @param res 结果列表
* @return 返回是否能进行拓扑排序
*/
public boolean topologySort(Activity[] as,ArrayList<Activity> res){
//PriorityQueue构造函数,初始容量、比较器
PriorityQueue<Activity> pq = new PriorityQueue<Activity>(20,new ActiComparator());
for(int i=1;i<as.length;i++){
if(as[i].inCnt==0){
pq.add(as[i]);
}
}
while(!pq.isEmpty()){
Activity a = pq.poll();
res.add(a);
for(int i=0;i<a.adjs.size();i++){
a.adjs.get(i).inCnt--;
if(a.adjs.get(i).inCnt==0){
pq.add(a.adjs.get(i));
}
}
}
if(res.size() != as.length){ //结果列表的大小不等于邻接表长度
return false;
}
return true;
}
public static void main(String[] args) {
TS ts = new TS();
Activity[] as = ts.init();
ArrayList<Activity> list = new ArrayList<Activity>();
ts.topologySort(as, list);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
}
参考《算法的乐趣》王晓华
总结:需要事先有图的邻接表,事先知道入度,其他信息不需要