C++拓扑排序

有向无环图

定义

没有环的有向图,也称为DAG

有向无环图

性质

性质一

从任意一点进行遍历,必不会陷入死循环

性质二

入度为0的点信息确定,新的入度为0的点信息也确定

拓扑序

写法

1.将入度为0的数入列;

2.将出队的节点的出边所指向的点的入度-1,如果该节点的入度也为0,那么将该节点入队;

3.当所有的点都出队,那么DAG的遍历结束

这样的遍历顺序被称为拓扑序

例题

题面

洛谷P1807icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1807

示例代码

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
const int M=3e6+5;
int n,m;
int h[N],v[M],w[M],nx[M],tot,du[N],dis[N];
void add(int x,int y,int z){
	tot++;
	v[tot]=y;//存出边指向的点 
	w[tot]=z;//存边的长度 
	nx[tot]=h[x];//指向下一组数据 
	h[x]=tot; 
}
long long read() {
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') {
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}//快读 
void topsort(){
	queue<int>q;//建立队列 
	for(int i=1;i<=n;i++){
		dis[i]=-2e9;//赋初值为-2e9 
		if(du[i]==0)	q.push(i);//如果入度为0那么入对
	}
	dis[1]=0;//1到1的距离设为0 
	while (!q.empty()){
		int x=q.front();//获得队列头 
		q.pop();//队列头出队 
		for(int i=h[x];i;i=nx[i]){
			du[v[i]]-=1;//出边所指向的点入度-1 
			if(dis[x]!=-2e9)	dis[v[i]]=max(dis[v[i]],dis[x]+w[i]);
			//若出队的点不为初始值,证明与1相通,那么更新该点到1的距离 
			if(du[v[i]]==0 )	q.push(v[i]);
			//如果入度为0则加入队列 
		}
	}
}
int main(){
	n=read();
	m=read();
	for(int i=1;i<=m;i++){
		int x,y,z;
		x=read();
		y=read();
		z=read();
		add(x,y,z);//邻接表存储 
		du[y]+=1;//出边所指向的点入读加1 
	}
	topsort();
    if(dis[n]<=-1e9){
		cout<<-1;
        return 0;
	}
	// 无法抵达则不会被更新
	cout<<dis[n];//输出1到n的距离 
	return 0;
}
​

拓扑排序

在DAG中,使用拓扑序进行遍历,将结点的先后顺序输出就是拓扑排序;

 这张DAG有两种拓扑排序结果:
2->5->1->3->4

5->2->1->3->4

写法

我们只需要把出队后的点进行存储,再输出后就可以记录拓扑排序

例题

题面

描述

给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。

若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。

输入描述

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x
到点 y 的有向边 (x,y)。

输出描述

共一行,如果存在多个拓扑序列,则输出其中字典序最小的即可。

如果不存在拓扑序列,则输出 −1。

样例

输入

3 3
1 2
2 3
1 3

输出

1 2 3

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int h[N],v[N],nx[N],tot;
int dis[N],du[N];
priority_queue<int,vector<int>,greater<int> >q;
//优先队列(小根堆,也就是把队列内的数字从小往大排),因为是求最小字典序 
vector<int>ans;//动态数组存储拓扑排序 
void add(int x,int y){
	tot++;
	v[tot]=y;//存出边指向的点 
	nx[tot]=h[x];//指向下一组数据 
	h[x]=tot;
}
void bfs(){
	for(int i=1;i<=n;i++){
		dis[i]=-2e9;//赋初值为-2e9 
		if(du[i]==0)	q.push(i);//如果入度为0那么入对
	}
	while(!q.empty()){
		int x=q.top();//获得队列头
		q.pop();//队列头出队 
		ans.push_back(x); //将节点放入拓扑排序的内容中 
		for(int i=h[x];i;i=nx[i]){
			du[v[i]]--;//将出边指向的点的入度-1 
			if(du[v[i]]==0)	q.push(v[i]);//如果该点的入度为0则入队 
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		add(x,y);//邻接表存储 
		du[y]++;//出边指向的点入度加1 
	}
	bfs();
	if(ans.size()!=n){
		cout<<-1;
		return 0;
	}//如果拓扑排序访问过的数量小于或大于总点数,那么则没有拓扑排序 
	for(int i=0;i<ans.size();i++)	cout<<ans[i]<<' ';
	//输出最小字典序的拓扑排序 
	return 0;
}

 拓扑序的性质

拓扑排序:1-2-3-4-5-6

性质一 

任何一个数都无法更新在它位置之前的数(如5无法更新1,2,3,4;6无法更新1,2,3,4,5)

性质二

任何一个数都可以更新在它位置之后的数(如3可以更新3,4,5,6;1可以更新2,3,4,5,6)

例题

题面

洛谷P3074icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3074

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
const int M=5e4+5;
int n,m,ans;
int h[N],v[M],nx[M],tot,a[N],t[N];
int du[N];
queue<int>q;
long long read() {
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') {
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}//快速读入 
void add(int x,int y){
	tot++;
	v[tot]=y;//存出边指向的点 
	nx[tot]=h[x];//指向下一组数据 
	h[x]=tot;
}
void bfs(){
	for(int i=1;i<=n;i++){
		t[i]=a[i];//复制数组 
		if(du[i]==0)	q.push(i),ans=max(ans,a[i]);
		//入度为0入队,最大值也有可能是只挤一头奶牛的时间 
	}
	while(!q.empty()){
		int x=q.front();//获得队列头
		q.pop(); //队列头出队
		for(int i=h[x];i;i=nx[i]){
			t[v[i]]=max(t[v[i]],t[x]+a[v[i]]);
			//求挤完这头奶牛时要花的最短时间 
			ans=max(ans,t[v[i]]);
			//求挤完某头奶牛时所花的时间 
			du[v[i]]--;
			//将出边指向的点的入度-1 
			if(du[v[i]]==0)	q.push(v[i]);
			//如果该点的入度为0则入队 
		}
	}
}
int main(){
	n=read();
	m=read();
	for(int i=1;i<=n;i++)	a[i]=read();
	for(int i=1;i<=m;i++){
		int x,y;
		x=read();
		y=read();
		add(x,y);//邻接表存储
		du[y]++;//出边指向的点入度加1  
	}
	bfs();
	printf("%d",ans);//输出要花的时间 
	return 0;
}

拓扑序解决层次问题

把节点分成若干层次,可以描述某一节点事件执行的优先级。

拓扑排序能够保证优先级高的节点在优先级低的节点的前端。

层次节点编号
11
22,3
34,5,6

 

  • 17
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
拓扑排序是一种对有向无环图(Directed Acyclic Graph,简称DAG)中的节点进行排序的算法。在拓扑排序中,每个节点都有一个入度(InDegree)值,表示有多少条边指向该节点。拓扑排序的目标是按照节点的入度顺序将节点排序,使得每个节点的前驱节点都排在它的前面。 在给出的代码中,实现了一个拓扑排序算法。首先,通过构建邻接表来表示有向图,并初始化每个节点的入度为0。然后,从入度为0的节点开始,依次将节点加入拓扑序列中,并将其后继节点的入度减1。重复这个过程,直到所有节点都被加入到拓扑序列中,或者图中存在有向环。 具体步骤如下: 1. 定义一个队列node,用于存储当前入度为0的节点。 2. 遍历所有节点,将入度为0的节点加入队列node中。 3. 初始化拓扑序列的顶点个数number为0。 4. 当队列node不为空时,执行以下操作: - 弹出队列node中的一个节点u。 - 将number加1,表示拓扑序列顶点个数加1。 - 遍历节点u的所有后继节点v,将v的入度减1。 - 如果v的入度减为0,则将v加入队列node中。 5. 判断是否能产生拓扑序列,即判断n是否等于number。如果相等,则能产生拓扑序列;否则,存在有向环,无法产生拓扑序列。 综上所述,给定的C代码实现了一个拓扑排序算法,可以用于对有向无环图进行拓扑排序。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [C++拓扑排序](https://blog.csdn.net/qq_36935691/article/details/113853028)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [c++拓扑排序](https://blog.csdn.net/lukehong_/article/details/108208337)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值