DAG拓扑排序
DAG拓扑排序
引入
小学奥数类型题。
沏茶过程
(烧水壶) 到 (接水) 到 (烧水 洗茶杯 找茶叶)(并行) 到 (沏茶)
即有先后顺序的流程,且必须所有步骤都能执行。
概述
- 拓扑排序是对DAG(有向无环图)的顶点进行的一种线性排序,排序序列中每个顶点都会且仅会出现一次,且对于所有有向边 \(u\rightarrow v\),排序完后 \(u\) 都在 \(v\) 的前面。
- 如果图中存在环,就不能进行拓扑排序。
- 一个有向无环图可能有多种排序结果。
- 对于 \(n\) 个顶点的有向无环图:
- 最少存在 \(1\) 种合法的拓扑序列。
- 即该图为一条直线。
- 最多存在 \(n!\) 钟合法的拓扑序列。
- 即该图为散点,无边。
- 最少存在 \(1\) 种合法的拓扑序列。
- 对于 \(n\) 个顶点的有向无环图:
流程
- 在拓扑排序中,用 \(L\) 记录到目前为止的拓扑序列,用集合 \(S\) 记录所有不在 \(L\) 中入度为 \(0\) 的顶点。
- 步骤执行
- 首先遍历整张图上的顶点,如果一个顶点入度为 \(0\),将它加入 \(S\)。
- 当 \(S\) 不为空时:
- 在 \(S\) 中任取一个顶点 \(x\),将 \(x\) 加入到 \(L\) 的队尾,并把 \(x\) 从 \(S\) 中删去。
- 遍历从 \(x\) 出发的边 \(x\rightarrow y\),删除。如果 \(y\) 的入度变为 \(0\),则将其加入 \(S\) 中。
- 循环结束时:
- 如果所有点都加入了 \(L\),那么我们就找到了一个合法的拓扑序列。
- 如果有点不在 \(L\) 中,证明该图有环。
- \(L,S\) 用同一个队列维护。
- 时间复杂度 \(\operatorname O(n + m)\)
代码
-
STL
int main()
{
cin >> n >> m;
for (int _ = 0, x, y; _ < m; _++)
{
cin >> x >> y;
add_edge(x, y);
d[y]++;
}
int cnt = 0;
queue<int> q;
for (int i = 1; i <= n; i++) {
if (d[i] == 0) q.push(i), cnt++;//先加入入度为0的点
}
while(!q.empty()) {//拓扑排序
int u = q.front();
q.pop();
for (int i = head[u]; i; i = edge[i].next) {
int to = edge[i].to;
d[to]--;
if (d[to] == 0) q.push(to), cnt++;
}
}
if (cnt == n) YES;//如果n个点都排序成功了,说明无环
else NO;
}
```
- 手写
vector<int> edge[N + 1];
int n, m, q[N + 1], d[N + 1]//q为队列,d为入度;
void topoSort()
{
int front = 1, rear = 0;
for (int i = 1; i <= n; i++)
{
if (!d[i]) q[++rear] = i;
while(front <= rear)
{
int x = q[front];
++front;
for (auto to : edge[x])
{
if (--d[to] == 0)//没必要真的删边,维护d数组即可
{
q[++rear] = to;
}
}
}
}
if (rear == n)
{
printf("合法");
}
else
{
printf("不合法");
}
}
例题
-
- 利用拓扑排序思想来记忆化
dfs
。
#include<iostream> #include<algorithm> #include<cstdio> #include<vector> const int N = 1e4 + 10; std::vector<int> m[N]; int vis[N]; int n; int ans; int len[N]; int u, v; int dfs(int x) { if (vis[x]) return vis[x]; for (auto to : m[x]) vis[x] = std::max(vis[x], dfs(to)); vis[x] += len[x]; return vis[x]; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::cin >> n; for (int i = 1; i <= n; i++) { std::cin >> u >> len[i]; while(std::cin >> v && v != 0) m[v].push_back(u); } for (int i = 1; i <= n; i++) ans = std::max(ans, dfs(i)); std::cout << ans << std::endl; return 0; }
- 利用拓扑排序思想来记忆化
-
- 体会拓扑排序的作用
-
- 建构模型,形成图之后再拓扑。
-
- 对拓扑排序判断无解,判断最长拓扑链长度的方式的考察