关于拓补排序
拓补排序,可以简单理解成这样:
一个有向无环图(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;
}

浙公网安备 33010602011771号