6.2 拓扑排序
拓扑排序
在许多应用中,有向无回路图可用于抽象具有发生先后顺序的事件,图的搜索算法可以用于解决具有先决条件的问题。假设我们要安排一系列任务,但是只有在某个任务的先决条件具备时才能着手完成这个任务。我们希望以某种先后顺序组织这些任务,以便每项任务都是在先决条件已完成的前提下逐个完成。
因为任务之间存在先决条件限制,也就是顶点之间存在方向性,所以这一类问题可以用有向无环图(DAG)来描述。如图给出一个学科学习的例子,其中必须先学完某些功课才能学习其它功课,当然也有一些比较独立的功课,例如体育课。图中有向边(u, v)表示功课u必须在功课v之前学习。所以该图的拓扑排序将可以给出一个功课学习的先后顺序。
图 功课学习先后顺序图
事实上拓扑排序是有向无环图深度优先搜索的直接应用。假设对某一有向无回路图G=(V, E)运行DFS,并确定其所有顶点的搜索完成时间f。图中某一条边(u, v) 在DFS过程中,f[u]必定大于f[v]。所以只要将图中各顶点按照DFS搜索所获得的搜索完成时间由大到小进行排序即可完成拓扑排序。
根据上面的分析,拓扑排序算法实现如下:
* 图的拓扑排序算法,排序后各顶点的标号按顺序排放至
* 数组,并将该数组返回。
* @param g 带排序的有向无环图
* @return 排序后顶点存放至数组
*/
public static int[] topologocal_sort(GraphLnk g){
// 首先对图进行深度优先搜索
GraphSearch.DFS(g);
int n = g.get_nv();
int[] f = new int[n];
// 各顶点深度优先搜索时的第二个时间
for(int i = 0; i < n; i++){
f[i] = GraphSearch.f[i];
}
// 创建数组,用于放置排序后各顶点的标号
int r[] = new int[n];
// 按照f值由大到小将各顶点排序
// 遍历n次
for(int i = 0; i < n; i++){
int _max = f[0], _max_idx = 0;
// 每次遍历寻找f值最大的下标
for(int j = 1; j < n; j++)
if(f[j] > _max){
_max = f[j];
_max_idx = j;
}
// 将找到的最大值重赋值为无穷小
f[_max_idx] = -Integer.MAX_VALUE;
// 将序号存放置数组r
r[i] = _max_idx;
}
return r;
}
算法首先对图进行DFS搜索,并获得各顶点完成遍历的时间f。然后,将各顶点的序号逐个取出并放置到待返回的数组中。顶点取出的顺序根据顶点的f时间值由大到小决定。
图各顶点标号如图,顶点经过DFS后各顶点的搜索完成时间d[u]/f[u]为:
0)1/14 |
1)15/16 |
2)17/18 |
3)2/7 |
4)8/13 |
5)9/12 |
6)3/6 |
7)10/11 |
8)4/5 |
图 功课学习先后顺序对应的有向无环图
调用拓扑排序函数对该图进行拓扑排序:
String course[] = {"语文", "算术", "体育","英语",
"数学", "基础物理","基础物理英文",
"固态物理", "固态物理英文"};
// 从文件获取图的信息
GraphLnk GL =
Utilities.BulidGraphLnkFromFile("Graph\\graph5.txt");
// 对图进行拓扑排序
int od[] = TopologicalSort.topologocal_sort(GL);
// 打印拓扑排序后顶点顺序
for(int i = 0; i < od.length; i++){
System.out.print(od[i] + ". " + course[od[i]]+" ");
}
排序后各顶点的顺序为:
2. 体育 1. 算术 0. 语文 4. 数学 5. 基础物理 7. 固态物理 3. 英语 6. 基础物理英文 8. 固态物理英文
按照这个顺序可以将图中每个顶点沿水平方向形成一个顶点序列,使得图中有向边都由左指向右。
图 图中功课学习拓扑排序结果图
后记
拓扑排序有一个很重要的应用:在c/c++语言中,我们的工程中常常会包含很多头文件,头文件有会include别的头文件,所以这些头文件之间形成一个有向图。编译器在编译时需要确定文件的编译先后次序。aha,拓扑排序可以用来为头文件进行排序!