拓扑排序入门学习笔记

拓扑排序


一、预备知识

DAG图:

一个无环的有向图,我们称之为有向无环图(directed acycline graph),简称DAG图。
DAG是描述一项工程或系统进行过程的有效工具,对于一项工程可以分为若干活动,每个活动之间可能有时间上的先后顺序,我们可以用一条有向边来描述活动间的先后顺序。

简单地说:DAG图是有向的,而且在这个图中不能出现环,就像这样:
DAG
如果节点1与节点2相连接,那么这样的图就不是DAG了,因为在这种情况下,图中出现了一个环。

DAG图的结构可以看成要完成一个游戏关卡,在完成了其中一个任务后可以前往下一个任务。在上面这个图中,如果我们完成了任务5,那么下一步就可以去完成任务2或任务4。

我们也可以理解为在完成任务5的时候,触发了任务2和任务4,我们要去完成它们,在完成了整个游戏后,我们回忆起之前的通关步骤,就可以得到一个DAG图。所以DAG图可以描述活动间的先后顺序

AOV网:

用顶点表示活动,用有向边表示活动间的先后关系的有向图,我们称之为顶点表示活动网络(Activity On Vertex Network),简称AOV网
……
对于AOV网,我们往往关心的是能否找到一个合理的活动安排顺序,从而使得工程顺利进行,这在有向图中对应拓扑排序操作

从上面的话可以得知,拓扑排序的作用就是找到一个合理的节点排列,使得工程顺利进行,这就像是做游戏攻略了。


二、拓扑排序概念

对一个有向无环图G进行的拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列我们称之为满足拓扑次序的序列(Topological Order),简称拓扑排序。

拓扑排序就是将所有节点排列成一个具备以下性质的序列:

①图中所有的有向边均是从左指向右的
②若图中存在有向环,则不可能使顶点满足拓扑次序
③一个DAG的拓扑排序通常表示某种方案切实可行
④一个DAG可能有多个拓扑排序

根据上面的性质,我们可不可以画出上图的拓扑排序呢?自己动手画一画,会得到一个这样的图:
拓扑排序
那么这个图的拓扑排序就是:52431
当然,还有一下几种情况:
54231
52341
52314


三、拓扑排序算法

拓扑排序有好几种算法实现呢!这里我们只详细讲解其中两种,还有一些其他算法在后面也会给出代码和简略的解说。

拓扑排序的开头在哪里?

通过前面的学习,我们知道能进行拓扑排序的只有DAG图,它是有向图,如果随便选择一个起点,那么有可能有一些节点永远也遍历不了

我们再仔细思考一下,什么情况下有一些节点永远也遍历不到呢?比如像这种情况(以上图做例子),我们选取2为起点,那么4和5将无法遍历,但是我们选取5为起点,就可以完成拓扑排序,此时我们发现:5没有前驱,2有前驱
由此我们可以得出结论:只要是入度为0的节点都可以作为起点

开头找到后后面的节点怎么遍历?

我们不妨将后面的节点“变成”拓扑排序的开头
这句话什么意思呢?继续上面的情况,5已经被作为起点,计入答案了,此时我们可以将5这个节点删除,并且将与5连接的边统统删去,同时更新每个节点的入度。这时我们发现,4的入度为0,2的入度为0,拓扑排序的第二个节点就是2或4了。

我们只要一直循环以上两步,最后就可以获得拓扑排序后的序列。

代码实现:
#include<iostream>
using namespace std;
int n,m,head[105],x,y,cnt,ans[105];
bool vis[105];//访问记录
int in[105];//记录入度
struct edge{
	int next;
	int to;
}a[505];

void addedge(int from,int to)//连边建图
{
	cnt++;
	a[cnt].next=head[from];
	a[cnt].to=to;
	head[from]=cnt;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y;
		addedge(x,y);
		vis[x]=1;
		vis[y]=1;//还未访问过
		in[y]++;//统计入度
	}
	for(int i=1;i<=n;i++)
	{
		int j=1;
		while((j<=n)&&(in[j]>0||vis[j]==0)) j++;//寻找一个入度为0且仍在图上的点
		vis[j]=0;//删除这个点
		for(int k=head[j];k!=0;k=a[k].next)
			in[a[k].to]--;//删去与这个点连接的边,更新入度值
		ans[i]=j;//计入答案
	}
	for(int i=1;i<=n;i++)
		cout<<ans[i]<<' ';
	return 0;
}

这一种做法叫做无前驱顶点优先的拓扑排序算法
当然,有一种做法叫做无后缀顶点优先的拓扑排序算法
这一种算法就找出度为0的点,然后答案要倒着输出,其它部分都与前面做法一样。

接下来介绍一种求图中所有拓扑排序的算法 (说白了就是回溯递归)
我们利用回溯生成图中所有节点的排列方案,同时又使这些点的顺序符合拓扑排序即可。

代码实现:
#include<iostream>
using namespace std;
int n,m,head[105],x,y,cnt,ans[105];
bool vis[105];//访问记录
int in[105];//记录入度
struct edge{
	int next;
	int to;
}a[505];

void addedge(int from,int to)//连边建图
{
	cnt++;
	a[cnt].next=head[from];
	a[cnt].to=to;
	head[from]=cnt;
}

void topu(int now)
{
	if(now>n)
	{
		for(int i=1;i<=n;i++)
			cout<<ans[i]<<' ';
		return ;
	}
	for(int i=1;i<=n;i++)
		if(!vis[i]&&in[i]==0)
		{
			ans[now]=i;//计入答案
			vis[i]=0;//删除这个点
			for(int k=head[i];k!=0;k=a[k].next)
				in[a[k].to]--;//删去与这个点连接的边,更新入度值
			tuopu(now+1);
			for(int k=head[i];k!=0;k=a[k].next)
				in[a[k].to]++;//恢复原来的值继续回溯
			vis[i]=1;
		}
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y;
		addedge(x,y);
		vis[x]=1;
		vis[y]=1;//还未访问过
		in[y]++;//统计入度
	}
	dfs(1);
	return 0;
}

本文最主要介绍的就是这两种算法了,除此以外还有用栈实现的
用栈实现
dfs时间戳实现的
dfs实现1
dfs实现2


三、知识总结

拓扑排序是出现在DAG图中,用于描述节点被访问的先后顺序的序列。
使得图中任意一对顶点u和v,若<u,v>∈E(G),则u在线性序列中出现在v之前。

算法实现:

  1. 找入度为0的点
  2. 加入序列
  3. 删去这个点且删去与它相连的边(即设置此点为“已访问”状态,与此点相连的点的入度减1)
  4. 循环直至所有点已在序列中,输出答案

拓扑排序将原本交错的图转化为序列,使得图的层次清晰,顺序明确,图上DP更加方便

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值