DAG拓扑排序

DAG拓扑排序

引入

小学奥数类型题。

沏茶过程

(烧水壶) 到 (接水) 到 (烧水 洗茶杯 找茶叶)(并行) 到 (沏茶)

即有先后顺序的流程,且必须所有步骤都能执行。

概述

  • 拓扑排序是对DAG(有向无环图)的顶点进行的一种线性排序,排序序列中每个顶点都会且仅会出现一次,且对于所有有向边 \(u\rightarrow v\),排序完后 \(u\) 都在 \(v\) 的前面。
  • 如果图中存在环,就不能进行拓扑排序。
  • 一个有向无环图可能有多种排序结果。
    • 对于 \(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("不合法");
    }
}

例题

  • P1113 杂务

    • 利用拓扑排序思想来记忆化 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;
      }
    
  • P4017 最大食物链计数

    • 体会拓扑排序的作用
  • P1983 NOIP2013 普及组 车站分级

    • 建构模型,形成图之后再拓扑。
  • P1347 排序

    • 对拓扑排序判断无解,判断最长拓扑链长度的方式的考察
posted @ 2023-11-29 13:17  加固文明幻景  阅读(16)  评论(0编辑  收藏  举报