有向图的拓扑排序

概念及模板

有向图的拓扑排序是指将有向无环图中的所有顶点排成一个线性序列,使得图中任意一对顶点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;
}

例题 杂务

杂务

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

此题只需要记录每个人物最快完成的时间即可:将所有入度为 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;
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值