学习记录:拓扑排序
概念:
拓扑排序是对有向无环图的顶点的一种排序。
有向无环图(Directed Acyclic Graph)(DAG):
从任意顶点出发无法返回到出发点,即为有向无环图
它使得如果存在一条从(v_i)到(v_j)的路径,那么在排序中(v_j)出现在(v_i)的后面。
可以这样想,看成一张技能树。只有在点好了低级技能后,才能点高阶技能。这样想可以很容易明白接下来拓扑排序的两个特征。
- 在图含有圈的情况下,拓扑排序是不可能的。
如果技能树是个圈的话,高级技能需要低级技能作为前提,反之亦然,这就成死循环了。 - 拓扑排序不是唯一的。
只要能点完技能树,那么每一种都是拓扑排序。要是点的先后顺序唯一,那就没有各种build可言了
(搞张老滚5的图~)
代码实现
Kahn 算法
初步实现
- 先找出任意一个没有进入边的顶点,并打印该点。
- 删除1中顶点的所有出边,重复1,2直到全部打印完成。
- 最终打印顺序即为拓扑排序的结果
要注意的是,如果最后不存在没有进入边的顶点,但是还存在边,那么这个图一定是有环图
//《数据结构与算法分析——C语言描述》中的伪代码,稍微改了一下
void Topsort(Graph G) //给一张图
{
Vertex V, W; //定义两个顶点
for (int i = 0; i < Num_Vertex; i++) //遍历 顶点数 遍
{
V = FindVertex(); //找到入度为0且尚未排好序的顶点
if (!V) //如果这样的点不存在,那么存在环
{
printf("Graph has a cycle");
return;
}
Topnum[V] = i; //给定拓扑编号
for each W adjacent to V //对于任何(V_v,V_w)而言,W的入度-1
Indegres[W]--;
}
}
这样最简单的思想直接写出来的代码,FindVertex是对Indegres(入度数组)的一次遍历,调用花费(O(V))的时间,并且有(V)次调用,总的时间复杂度是(O(V^2))
改进版
如果此图是稀疏图,那么FindVertex中的遍历大部分是无效的。
我们可以维护一个集合——未排序且入度为0的顶点,那么FindVertex函数只需要在此集合中删除一个点即可。在最后降低入度的循环中,检查每一个相邻的顶点,在入度为0时,加入到集合中。
为了实现这个集合,可以选用栈,队列,都行。毕竟排序结果不唯一。
void Topsort(Graph G)
{
queue<vertex> q;//储存入度为0的队列
vertex V, W; //两个临时顶点
for (each vertex in G)//对于G中的每个顶点
if (Indegree(V) == 0)//入度为0
Q.push(V);//入队
int Count = 0;
while (!q.empty())
{
V = q.front();
q.pop();
TopNum[V] = ++Count;//排序
for each W adjacent to V//相邻的顶点
if (--Indegree[W]==0)//入度为0
q.push(W);//入队
}
if (count!=NumVerts)//并不是所有顶点都排序时
printf("Graph has a cycle");
}
使用邻接表时,该算法的复杂度降至(O(E+V))
代码:(这oj有毛病...)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int mod = 9973;
const int INF = 1 << 28;
const int maxn = 1e2 + 10;
int main()
{
int n, m;
int in[maxn];
vector<vector<int>> G;
while (cin >> n >> m && n)
{
G = vector<vector<int>>(n + 1);
memset(in, 0, sizeof(in));
for (int i = 0, a, b; i < m; i++)
{
cin >> a >> b;
in[b]++;
G[a].push_back(b);
}
queue<int> q;
int v;
for (int i = 1; i <= n; i++)
if (in[i] == 0)
q.push(i);
while (!q.empty())
{
v = q.front(), q.pop();
cout << v << ' ';
for (int i = 0; i < G[v].size(); i++)
if (--in[G[v][i]] == 0)
q.push(G[v][i]);
}
cout << endl;
}
return 0;
}