あいさか たいがblogAisaka_Taiga的博客
//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

拓扑排序详解

Toretto·2022-09-09 19:59·143 次阅读

拓扑排序详解

拓扑排序#

定义#

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

算法实现#

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

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

为什么要这么做呢?

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

代码及题目练习#

洛谷P1137

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

代码如下:

Copy
#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

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

代码如下:

Copy
#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 @   北烛青澜  阅读(143)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
目录