拓扑排序详解
拓扑排序
定义
拓扑排序是一种在有向无环图(也称 \(DAG\))上进行的一种排序,把其中的所有节点都排成一个序列,使得图中的任意一对有边相连的节点(\(u,v\))\(u\) 要出现在 \(v\) 前。
算法实现
-
找到图中没有前驱的节点(或者说是入度为 \(0\) 的节点)输入;
-
然后从图中将此节点删除并且删除以该节点为起点的边;
为什么要这么做呢?
上面已经提到,对于一条边(\(u,v\))要保证在拓扑排序里面要 \(u\) 出现在 \(v\) 的前面,而第一个数一定是一开始入度为 \(0\) 的点,当把所有的入度为 \(0\) 的点都放入后接下来就要找下面该放哪些,而因为先前放入的点入度都是 \(0\),所以要与之相连的点肯定出现在他的下面,所以我们直接把与已放入的点相连的点的入度减一,然后这时就会出现新的入度为 \(0\) 的点,就这样一直反复把所有的点都放入就可以了(应该讲明白了吧)。
代码及题目练习
这道题的题意已经很清楚了,就是问你到点 \(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;//好习惯
}
这道题刚拿到手没搞明白公式是什么意思,但仔细读了一下发现 \(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;//好习惯
}
虽然拓扑排序看上去很简单,但正是因为他太简单了所以很灵活,有很多题一般是看不出来要用拓扑排序来做的,当你遇到某一个题目的时候可以找一下隐藏条件,如果他是一个有向无环图的话就可以优先考虑一下拓扑排序。
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/p/16673850.html
The heart is higher than the sky, and life is thinner than paper.