【6】拓扑排序学习笔记
前言
有向无环图和拓扑排序直接关联到中后期的图论建模思想,是很重要的基础知识。这个如果不彻底弄懂,以后图论会很困难。
有向无环图
正如其名,一个边有向,没有环的图,也叫DAG。
DAG图实际运用:描述含有公共子式的表达式及工程或系统的进行过程时的有效工具。
一个较大的工程被分成若干个子工程,这些子工程被称作活动。活动之间存在某种约束关系,用有向边来表示。
关心的问题:
:工程能否顺利进行
:估算整个工程完成所需要的最短时间
拓扑排序
用于在一个DAG表示的工程的的前后驱约束关系中,对完成活动的先后进行排序,使工程顺利进行。
拓扑排序的步骤:
:选择图中所有入度为 的点,加入栈/队列。
:取出栈顶/队首的点加入拓扑序列末尾,删除这个点以及其出边(出边所到点的入度减 ),弹栈/队列。
:在出边到达的点中选择入度为 的点,加入栈/队列。
:重复 ,直到栈/队列空。
如果拓扑排序结束后拓扑序列有 ( 为图的顶点数)个元素,那么图中无环;否则,图中必定存在环。
时间复杂度:
void topo_sort()
{
int sta[300000],top=0;
for(int i=1;i<=n;i++)
if(ind[i]==0)sta[++top]=i;
while(top)
{
int now=sta[top];
top--;
for(int i=h[now];i;i=e[i].next)
{
ind[e[i].v]--;
if(ind[e[i].v]==0)sta[++top]=e[i].v;
}
ou[++ans]=now;
}
}
拓扑排序例题
例题 :
拓扑排序板子题,不多赘述。
#include <bits/stdc++.h>
using namespace std;
struct edge
{
int v,next;
}e[300000];
int n,m,h[300000],cnt=0,ans=0,ind[300000],ou[300000];
void add_edge(int u,int v)
{
e[++cnt].v=v;
e[cnt].next=h[u];
h[u]=cnt;
ind[v]++;
}
void topo_sort()
{
int sta[300000],top=0;
for(int i=1;i<=n;i++)
if(ind[i]==0)sta[++top]=i;
while(top)
{
int now=sta[top];
top--;
for(int i=h[now];i;i=e[i].next)
{
ind[e[i].v]--;
if(ind[e[i].v]==0)sta[++top]=e[i].v;
}
ou[++ans]=now;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int t=-1;
while(t!=0)
{
scanf("%d",&t);
if(t==0)break;
add_edge(i,t);
}
}
topo_sort();
if(ans!=n)printf("-1");
else
for(int i=1;i<=ans;i++)
printf("%d ",ou[i]);
return 0;
}
例题 :
前置知识:【6*】差分约束系统学习笔记
以每个人的奖金数为点构造差分约束系统,由于题目要求至少,需要求最长路,由于边权只有 ,可以通过拓扑排序直接求解最长路。
#include <bits/stdc++.h>
using namespace std;
struct edge
{
int v,next;
}e[300000];
int n,m,h[300000],cnt=0,ans=0,ind[300000],ou[300000],dis[300000],sum=0;
void add_edge(int u,int v)
{
e[++cnt].v=v;
e[cnt].next=h[u];
h[u]=cnt;
ind[v]++;
}
void topo_sort()
{
int sta[300000],top=0;
for(int i=0;i<=n;i++)
if(ind[i]==0)sta[++top]=i;
while(top)
{
int now=sta[top];
top--;
for(int i=h[now];i;i=e[i].next)
{
ind[e[i].v]--;dis[e[i].v]=max(dis[e[i].v],dis[now]+1);
if(ind[e[i].v]==0)sta[++top]=e[i].v;
}
ou[++ans]=now;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add_edge(y,x);
}
for(int i=1;i<=n;i++)
dis[i]=-1;
dis[0]=99;
for(int i=1;i<=n;i++)
add_edge(0,i);
topo_sort();
if(ans!=n+1)printf("Poor Xed");
else
{
for(int i=1;i<=ans;i++)
sum+=dis[i];
printf("%d",sum);
}
return 0;
}
例题 :
图论建模题目,第一眼看上去像DP。
由于在一段内,车所经过的车站必然比没有经过的车站优先级高,利用这一点可以以每个车站的优先级为点构造差分约束系统,由于题目要求最少,需要求最长路,同时边权依旧只有 ,可以直接使用拓扑排序。
至于建边,可以把有效行程之内未经过的车站到每一个经过了的车站建一条边,表示一个大于关系。同时这样可能会有很多重边,可以通过记忆化,排除冗余。
当然,此题有复杂度更优秀的做法,可以参考其他题解。
#include <bits/stdc++.h>
using namespace std;
struct node
{
int v,next,dis;
}e[1000010];
int n,m,s,h[1010],cnt=0,z[1010],book[1010],dis[1010],ind[1010],vis[1010][1010],ans=0;
void add_edge(int f,int v,int dis)
{
e[++cnt].next=h[f];
e[cnt].v=v;
e[cnt].dis=dis;
h[f]=cnt;
ind[v]++;
}
void topo_sort()
{
int sta[300000],top=0;
for(int i=0;i<=n;i++)
if(ind[i]==0)sta[++top]=i;
while(top)
{
int now=sta[top];
top--;
for(int i=h[now];i;i=e[i].next)
{
ind[e[i].v]--;dis[e[i].v]=max(dis[e[i].v],dis[now]+e[i].dis);
if(ind[e[i].v]==0)sta[++top]=e[i].v;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d",&s);
for(int j=0;j<s;j++)scanf("%d",&z[j]);
for(int j=0;j<s;j++)book[z[j]]=1;
for(int k=0;k<s;k++)
for(int j=z[0];j<=z[s-1];j++)
if(!book[j]&&!vis[j][z[k]])
{
add_edge(j,z[k],1);
vis[j][z[k]]=1;
}
memset(book,0,sizeof(book));
}
for(int i=1;i<=n;i++)
add_edge(0,i,0);
for(int i=1;i<=n;i++)
dis[i]=-99999999;
dis[0]=0;
topo_sort();
for(int i=1;i<=n;i++)
ans=max(ans,dis[i]);
printf("%d",ans+1);
return 0;
}
后记
一篇教练推荐的博客,写在这里,提供参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探