准备开始写一个系列的文章, 把ACM里常用的方法或经典的思路都总结一遍。
不过毕竟才刚刚起步,没有接触到什么高深的算法,所以先从最简单的拓扑排序开始写起吧
才疏学浅,以后如果有哪里讲得不清楚的敬请评论,大家一起讨论~
拓扑排序虽然是个比较简单的入门问题, 但是在许多题目中都需要用到, 所以还是来自己总结一下常用的拓扑排序方法吧
首先, 拓扑排序的概念:
在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。
- 每个顶点出现且只出现一次;
- 若A在序列中排在B的前面,则在图中不存在从B到A的路径。
也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面
——from WikiPedia
举个简单的例子就是有一系列的任务需要完成, 其中某些任务是需要做前置任务的, 否则就无法进行该任务,
拓扑排序就是把这些任务排出一个执行的顺序, 使得所有任务都能够按要求完成(但前提是没有循环的依赖关系,就是说B如果是排在A前面的,那么不能出现A在某个任务依赖关系中排在B的前面,也就是要求无环图)
注意到拓扑排序处理的一定是有向无环图(DAG)
常用的拓扑排序方法如下:(以后看到新的方法再更新)
方法一:最简单的, 按照拓扑排序的定义, 也就是每次都选择没有前置任务要求的任务,
这样(在没有环的条件下)一定能够完成所有的任务。
具体实现的方式就是,
①在所有顶点中选出一个入度为0的顶点输出(或者放进答案中), 若有多个入度为0的点则任选一个(这种情况下显然有多种排序方式)
②输出完之后,将该顶点删除(或入度置为Infinity),将该顶点通向的(也就是以当前顶点为起点的有向边所指向的)所有顶点的入度减1,继续步骤1
③若所有顶点都已经输出完毕了,结束排序
p.s.:如果找不到入度为0的顶点说明这个图不是DAG,即该有向图存在环,无法拓扑排序
这个方法比较直观,易于理解, 但是写起来需要记录和更新入度, 而且时间复杂度至少为N^2
如果用递归实现DFS的话,每到一个定点, 应该先DFS其所有子节点,DFS完之后再输出当前顶点。
如果用递归实现DFS的话,每到一个定点, 应该先DFS其所有子节点,DFS完之后再输出当前顶点。
如上图, 入度为0的点为V1, 把V1删除(输出),V2,V3,V4的入度就需要依次减1。
之后回到第一步继续找,下一个就是V2,以此类推。
p.s.:如果找不到入度为0的顶点说明这个图不是DAG,即该有向图存在环,无法拓扑排序
这个方法比较直观,易于理解, 但是写起来需要记录和更新入度, 而且时间复杂度至少为N^2
void TopSort(Vertex arr[],int N){
int cnt = 0;
while( cnt<n ){
sort_by_inDegree(arr,N);
cout << arr[0]->value << " ";
arr[0]->inDegree = INFINITY;
cnt++;
}
}
方法二:DFS深度优先搜索
如果之前没见过, 可能比较难以想到其具体的实现方法
这个方法的主要思路就是利用DFS之后对图生成一个若干棵树,从一棵树的最低层叶节点开始向上逐层输出(反向层序遍历)直到输出到根节点, 再输出其他树( 显然也会产生多种排序方式, 都是可行的)
原理就是因为把一棵树看做一个所有边都从子节点指向父节点有向图的话,树的叶节点都是入度为0的, 因此如果从底向上输出的话, 也相当于每次从为输出的顶点中选出入度为0的点输出了。
如果用递归实现DFS的话,每到一个顶点, 应该先DFS其所有子节点,DFS完之后再输出当前顶点。显然DFS时要对顶点进行mark,若DFS到一个之前已经mark过的顶点,说明这个图不是DAG,存在环,无法拓扑排序。
还是以上图为例具体演示一下吧:
如先从V1进行DFS, 1->2->5->7->6, 到6已经没有下一个可行的结点了, 把6删除(输出), 向上回溯到7, 同样没有下一个结点了(6被标记为删除了),
于是删除7, 向上回溯到5, 发现从5还可以走到4, 接着4->3 ( 6被删除了), 又没有路了, 仍然是删除3, 回溯, 删除4, 回溯, 删除5, 回溯, 删除2, 回溯, 删除1
所有结点都删除完毕, 就得到了一个排序好的删除序列 : 6 7 3 4 5 2 1, 这个删除序列的逆序即为我们所要求的拓扑排序的结果
void DFS(int v)
{
isVisited[i] = true;
for(int i=0; i<n; i++){
if( Edge[v][i]==true && !isVisited[i] ){
DFS(i);
}
}
ans.push(v);
}
void TopSort(){
memset(isVisited,0,sizeof(isVisited));
for(int i=0; i<n; i++){
if( !isVisited[i] ) DFS(i);
}
Pop_Stack(ans); // 逆序输出stack容器ans
}
本文旨在深入浅出地介绍拓扑排序的基本概念及其应用,从简单的概念出发,逐步引导读者掌握拓扑排序的方法。通过一系列实例解析,帮助读者理解如何在有向无环图中进行拓扑排序,并提供了两种实现方法:一种是基于入度的迭代方法,另一种则是使用深度优先搜索的递归方法。同时,文章还强调了拓扑排序在解决实际问题中的重要性,如任务安排等场景。
2304

被折叠的 条评论
为什么被折叠?



