图的拓扑排序详解

定义:

        拓扑排序:图中的点以线性方式进行排序。即若图中有一条边是u->v,则最后的排序结果中u总在v前面。类似于先序图的概念,必须先到达u点才能到达v点,则u>v。

适用条件:

        并不是所有图都可以进行拓扑排序。这里有个重要的概念是离散数学中的偏序关系。

        偏序的大意就是,如果图中的两个点有确定的大小关系,例如一条边上的两个点,或没有直接的联系,即两个点不相通,则就是偏序关系。不能存在互相矛盾的关系,比如环路,例如a->b,b->a,这种就不满足偏序关系。所以,只要一个图中不存在环路,不管图中的点连通与否,都满足偏序关系。而拓扑排序的条件就是这个图是有向无环图(DAG),有向是为了确定两个点的先后顺序,若无向,则关系都乱了。

唯一性:

        图中顶点的拓扑关系并不像数字的大小关系那样简单,这里还有个重要的概念的离散数学中的全序关系。

        全序关系在图中的大意就是,每个点都与其他所有点有着确定的先后顺序,那么这个图就满足全序关系。

        这里用数组与图做对比来解释,任意两个不相等的数字之间有着确定的大小关系,所以数组就满足全序关系,这样排序后的结果就是惟一的。但是通常我们评价排序算法时,要考虑一个因素是稳定性,即相同数字的排序结果是否和出现的顺序一致。因为两个相同的数字的大小关系无法确定,不满足全序关系,那么他们在排序结果中的位置也就无法确定。

        回到图中,如果DAG中的每个点都与其他所有点有确定的先后顺序,那么这个DAG的拓扑排序结果是惟一的,但是如果存在两个点不直接相连,即无法确定他们的拓扑关系,那么它们在拓扑结果中的位置就无法确定,拓扑结果就不唯一了。

基本思想:

        拓扑排序有两种做法,一种是bfs的思路,一种是dfs的思路。本文介绍bfs方法,bfs是以入度为切入点,一个点的入度越小,则排在它前面的点就越少。基本思想是动态维护一个入度为0的顶点的队列。然后取一个点出发,依次将图中与该点相连
的边去掉,如果在过程中又出现了新的入度为0的顶点,则加入队列中,直到队列为空。如果此时图中没有边了,则
不存在环路,从而得到排序结果,这也说明bfs方法可以检测一个图是不是有向无环图。

数据结构:

        算法中涉及到的数据结构有以下几种:

        1、图的邻接矩阵,用来表示图。

        2、保存排序结果的集合,一般用vector。

        3、入度数组,用来保存每个点的入度。

        4、队列,用来保存当前入度为0并且没有遍历到的点。

算法过程:

        1、建图

        2、遍历图中的所有点,统计每个点的入度,存到入度数组中

        3、将入度为0的点入队

        4、对当前队列中的点进行遍历,取出点并将它存到结果集中。再从该点出发,去掉图中以该点为起点的边,并且将边的终点的入度-1。同时判断终点的入度是否减为0,若为0,则入队。

        5、重复4,直到队列为空。

c++代码:

#include<iostream>
#include<vector>
#include<string.h>
#include<queue>
using namespace std;
vector<int> *edge;//边集 
vector<int> res;//结果集 
queue<int> q;
int in[101];//入度数组 
int n,m,edges;//edges表示此时图中所剩的边数 
int main()
{
	int a,b;
	cin>>n>>m;
	edges=m;
	edge=new vector<int>[n];
	memset(in,0,sizeof(in));
	for(int i=0;i<m;i++)
	{
		cin>>a>>b;
		edge[a].push_back(b);
	}
	//统计入度 
	for(int i=0;i<n;i++)
		for(int j=0;j<edge[i].size();j++)
			in[edge[i][j]]++;
	//将入度为0的点入队 
	for(int i=0;i<n;i++)
		if(in[i]==0)
			q.push(i);
	int n;
	while(!q.empty())
	{
		//取出一个点加入结果集 
		n=q.front();
		q.pop();
		res.push_back(n);
		//遍历该点所引出的所有边 
		for(int i=0;i<edge[n].size();i++)
		{
			//edges--表示去掉了这条边 
			edges--;
			//同时终点的入度-1 
			in[edge[n][i]]--;
			if(in[edge[n][i]]==0)
				q.push(edge[n][i]);
		}
	}
	//若此时图中还存在边,则说明有环路。 
	if(edges!=0)
		cout<<"Has circles!";
	else
		for(int i=0;i<res.size();i++)
			cout<<res[i]<<" ";
	return 0;
}

运行结果:

例子中的图有13个点,若是全序关系,则应该有(13-1)*13/2=78条边,显然这个图不满足全序关系,则排序结果不唯一。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值