当前位置:   article > 正文

有向图的拓扑排序_有向图 拓扑排序

有向图 拓扑排序

概念及模板

有向图的拓扑排序是指将有向无环图中的所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u, v)在图中,则u在该序列中排在v的前面。

例如,假设有n个任务,这些任务需要按照一定的顺序执行,同时有些任务必须在其它任务完成之后才能执行,此时可以使用拓扑排序来确定任务的执行顺序,从而保证所有任务都能够被按照正确的顺序执行完成。
比如:生成一个可执行程序,需要经历预处理,编译,汇编,链接四步,后面的步骤必须在前面的步骤完成后才能执行。

一个有向无环图,一定至少存在一个入度为 0 的点(抽屉原理)
当有向图中,某个点的入度为 0 的时候,说明是没有一个点指向这个点的,那这个点就能作为起点指向其他点了,遍历结束后,这个点就可以删掉了(将 t 放在当前的最前面了),并将它指向的所有点的入度都减 1

  1. 定义一个队列,将所有入度为 0 的点入队
  2. 循环,每次取队头 t,枚举 t 的所有出边 j,将 d[j]-- ,如果减完后 d[j] == 0,就将这个点入队,重复前面的操作,那么只要这个图无环,就能依次将它的所有点突破掉
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 100010;
int n, m;
int e[N], ne[N], h[N], idx;
int ret[N], p; // 记录拓扑序的数组
int d[N]; // 记录每个点的入度度数
void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
int to_polo()
{
    queue<int> q;
    for(int i = 1; i <= n; i++) 
        if(!d[i]) // 将所有入度为 0 的点全部入队列 
        {
            q.push(i);
            ret[p++] = i;
        }
    while(q.size())
    {
        int t = q.front();
        q.pop();
        for(int i = h[t]; i != -1; i = ne[i]) // 遍历所有出边 
        {
            int j = e[i];
            d[j]--;
            if(d[j] == 0) // 如果等于 0, 就加入队列 
            {
                q.push(j);
                ret[p++] = j;
            }
        }
    }
    if(p != n) return -1;
    else return 1;
}
int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while(m--)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        d[b] ++;
    }
    int ans = to_polo();
    if(ans == -1) cout << -1;
    else 
    {
        for(int i = 0; i < p; i++) cout << ret[i] << " ";
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

例题 杂务

杂务

题目中出现的,某一任务必须在某些任务完成后才能完成,这就是拓扑排序的标志

此题只需要记录每个人物最快完成的时间即可:将所有入度为 0 的点放入队列,然后遍历所有出边,将遍历到的点的入度都减1,当一个点的入度减到 0 的时候,就可以加入队列。
加入队列后,这个点的最快时间就可以被更新了,最终答案在所有任务完成的最快时间中取最大值即可!

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 10100;
const int M = 100100;
int n;
int e[M], ne[M], h[N], idx;
int ti[N]; // id -> time 每个点所需要的时间
int Com[N]; // 记录每个点完成的最快时间 
int d[N]; // 记录某个点的入度个数 
int ans; // 答案
void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}
void to_polo()
{
	queue<int> q;
	for (int i = 1; i <= n; i++)
	{
		if (d[i] == 0)
		{
			q.push(i);
			Com[i] = ti[i];
			ans = max(ans, Com[i]);
		}
	}
	while (q.size())
	{
		int t = q.front();
		q.pop();
		for (int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			Com[j] = max(Com[t], Com[j]); // 必须考虑最差情况
			d[j]--;
			if (!d[j])
			{
				Com[j] += ti[j];
				ans = max(ans, Com[j]);
				q.push(j);
			}
		}
	}
	cout << ans;
}
int main()
{
	memset(h, -1, sizeof h);
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int id, tim, tmp;
		cin >> id >> tim;
		ti[id] = tim;
		while (cin >> tmp)
		{
			if (tmp == 0) break;
			add(tmp, id);
			d[id]++;
		}
	}
	to_polo();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/874056
推荐阅读
相关标签
  

闽ICP备14008679号