关于拓补排序

拓补排序,可以简单理解成这样:

一个有向无环图(DAG),对于任意一条出现的边 \((u,v)\),在排序的结果中保证 \(u\)\(v\) 的前面。

对于这个 DAG,有拓补排序 \(2 \to 8 \to 0 \to 3 \to 7 \to 1 \to 5 \to 6 \to 9 \to 4 \to 11 \to 10 \to 12\),供手推。

(源自oi-wiki)

拓补排序,多用 Kahn -> 奇怪的算法。

有一任意顺序的队列 \(S\),初始化为所有入度为 0 的节点。

每次操作,都从 \(S\) 中取出一个入读为 0 的节点 \(u\),先将 \(u\) 推入列表 \(Ans\) 中,再将 \(u\)(以及与 \(u\) 有关的边 \(\to\) 其实就是所有 \((u,v_i)\) 边,因为已经保证入读为 0)删掉。

操作直到 \(S\) 为空。

若在图中依然有 点 or 边,即代表这不是 DAG。(也可以写成 \(Ans\) 没有排完 \(n\) 个节点)

合理性证明

考虑一个图,删掉某个入度为 的节点之后,如果新图可以拓扑排序,那么原图一定也可以。反过来,如果原图可以拓扑排序,那么删掉后也可以。

(迷,但有点不迷,至少会了)

注: 这样排出的序并不是整体有序的(就是有可能还有字典序更小/大的排列)。

要是要求字典序最小的,直接将队列 \(S\) 改为优先队列即可(直接比较元素序号大小)。

Ans ← 包含已排序元素的空列表
S ← 所有 入度=0 节点 的集合/队列
while S 非空 do
	从 S 中删除节点 n
	将 n 插入 Ans
	对于具有从 n 到 m 的 边 e 的每个节点 m,(即对于所有边 e(n,m) ) do
		从图中删除 边 e
		如果 m 入度也为 0,则
			将 m 插入 S
if 图 有边 那么
	返回错误(图形至少有一个环 (DAG))
else
	返回 Ans(拓扑排序顺序)

模板:

bool toposort() {
	queue<int> q;
	for (i = 0; i < n; i++)
		if (in_deg[i] == 0) q.push(i);  //in_deg[i] 为节点 i 的度数
	vector<int> ans;
	while (!q.empty())
    {
		int u = q.front();q.pop();
		ans.push_back(u);
		for each edge(u, v)  //此处为伪代码
        {
			if (--in_deg[v] == 0) q.push(v);
		}
	}
	if (ans.size() == n)  //排完了
    {
		for (i = 0; i < n; i++)
			cout << ans[i] << endl;
		return true;
	}
    else return false;
}
posted @ 2021-10-08 21:10  Lotuses-robot  阅读(177)  评论(0)    收藏  举报