//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

拓扑排序详解

拓扑排序

定义

拓扑排序是一种在有向无环图(也称 \(DAG\))上进行的一种排序,把其中的所有节点都排成一个序列,使得图中的任意一对有边相连的节点(\(u,v\)\(u\) 要出现在 \(v\) 前。

算法实现

  1. 找到图中没有前驱的节点(或者说是入度为 \(0\) 的节点)输入;

  2. 然后从图中将此节点删除并且删除以该节点为起点的边;

为什么要这么做呢?

上面已经提到,对于一条边(\(u,v\))要保证在拓扑排序里面要 \(u\) 出现在 \(v\) 的前面,而第一个数一定是一开始入度为 \(0\) 的点,当把所有的入度为 \(0\) 的点都放入后接下来就要找下面该放哪些,而因为先前放入的点入度都是 \(0\),所以要与之相连的点肯定出现在他的下面,所以我们直接把与已放入的点相连的点的入度减一,然后这时就会出现新的入度为 \(0\) 的点,就这样一直反复把所有的点都放入就可以了(应该讲明白了吧)。

代码及题目练习

洛谷P1137

这道题的题意已经很清楚了,就是问你到点 \(i\) 的最长路径长度为多少,这道题也差不多是个板子题,只要注意在拓扑排序的时候把当前点的最大深度更新一下就好了。

代码如下:

#include<bits/stdc++.h>
#define N 1000010
using namespace std;
int n,m,cnt,head[N],dep[N],du[N];//dep存放每一个点的最大深度,du存放每一个点的入度 
struct edge{int v,next;}e[N<<1];//链式前向星存边 
inline void add(int u,int v)//加边函数 
{
	e[++cnt].v=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void topsort()//拓扑排序主体 
{
	queue<int>q;//队列辅助进行拓扑排序 
	for(int i=1;i<=n;i++)//一开始先遍历每一个点 
	  if(!du[i])//如果入度为零 
	    q.push(i),dep[i]=1;//直接入列,标记深度为1 
	while(!q.empty())//只要队列不空就还有点没有排好序 
	{
		int u=q.front();//取出队头元素 
		q.pop();//弹出 
		for(int i=head[u];i;i=e[i].next)//遍历每一条与u相连的边 
		{
			int v=e[i].v;//取出终点 
			dep[v]=dep[u]+1;//更新终点的最大深度 
			du[v]--;//终点入度减一,相当于把此边删除 
			if(!du[v])//如果当前终点的入度为0了 
			  q.push(v);//入列 
		}
	}
	return ;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);//存有向图 
		du[v]++;//终点入度加一 
	}
	topsort();//进行拓扑排序 
	for(int i=1;i<=n;i++)
	  cout<<dep[i]<<endl;//输出答案 
	return 0;//好习惯 
}

洛谷P1038

这道题刚拿到手没搞明白公式是什么意思,但仔细读了一下发现 \(u_{i}\) 一开始已经给出了,而且每一个 \(c_{i}\) 只要减去一个 \(u_{i}\) 就可以了,其中有几个点要注意一下:一是如果起点的 \(c_{i}\) 小于等于 \(0\) 的话终点在套公式计算 \(c_{i}\) 的时候是不能累加当前边的,因为起点的神经节不兴奋,无法向终点传输,而是在建边的时候开一个数组来标记每一个点的出度,这样的话就好判断每一个点是不是输出层了。

代码如下:

#include<bits/stdc++.h>
#define int long long
#define N 1010
using namespace std;
int n,m,ci[N],ui[N],head[N<<4],cnt,du[N],ou[N];//ciui如题所示,du记录入度,ou记录出度 
struct edge{int v,w,next;}e[N<<4];//结构体存边 
queue<int>q; 
inline void add(int u,int v,int w)//加边函数 
{
	e[++cnt].w=w;
	e[cnt].v=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void topsort()//拓扑排序主体 
{
	while(!q.empty())//只要队列不空就没排完 
	{
		int u=q.front();q.pop();//取出队头元素并弹出 
		for(int i=head[u];i;i=e[i].next)//遍历每一条与之相连的边 
		{
			int v=e[i].v;//取出终点 
			du[v]--;//终点入度减一 
			if(ci[u]>0)//如果当前起点的ci大于0才能往终点传输 
			  ci[v]+=e[i].w*ci[u];//计算累加求和 
			if(!du[v])//如果终点的入度为0了 
			{
				q.push(v);//把终点放入队列 
				ci[v]-=ui[v];//减去ui 
			}
		}
	}
	return ;
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
	  cin>>ci[i]>>ui[i];//输入当前点的信息 
	  if(ci[i]>0)q.push(i);//只有ci大于0的才能向其他点传输 
    }
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);//建有向图 
		du[v]++;//入度,拓扑用 
		ou[u]++;//出度,判断是不是输出层 
	}
	topsort();//拓扑排序 
	int flag=0;//flag标记是否有输出层不为0的点 
	for(int i=1;i<=n;i++)//遍历每一个点 
	  if(ci[i]>0&&!ou[i])//如果是输出层且ci大于0 
		flag=1,//标记flag 
		cout<<i<<" "<<ci[i]<<endl;//输出 
	if(!flag)//如果flag是0 
	  cout<<"NULL"<<endl;//输出NULL 
	return 0;//好习惯 
}

虽然拓扑排序看上去很简单,但正是因为他太简单了所以很灵活,有很多题一般是看不出来要用拓扑排序来做的,当你遇到某一个题目的时候可以找一下隐藏条件,如果他是一个有向无环图的话就可以优先考虑一下拓扑排序。

参考博客

posted @ 2022-09-09 19:59  北烛青澜  阅读(98)  评论(0编辑  收藏  举报