拓扑排序(一)/SDNU1089

拓扑排序(一) /SDNU1089

1、定义(以下摘自百度百科)

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

2.预备知识(摘自百度百科)

一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。

一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行。如图3-6是一个具有三个顶点的回路,由<A,B>边可得B活动必须在A活动之后,由<B,C>边可得C活动必须在B活动之后,所以推出C活动必然在A活动之后,但由<C,A>边可得C活动必须在A活动之前,从而出现矛盾,使每一项活动都无法进行。这种情况若在程序中出现,则称为死锁或死循环,是应该必须避免的。

在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。
由AOV网构造出拓扑序列的实际意义是:如果按照拓扑序列中的顶点次序,在开始每一项活动时,能够保证它的所有前驱活动都已完成,从而使整个工程顺序进行,不会出现冲突的情况。

3.题目

链接:http://sdnuoj.rainng.com/problem/show/1089

题目

思路

1.在有向图中选一个没有前驱的顶点并且输出
2.从图中删除该顶点和所有以它为尾的弧(白话就是:删除所有和它有关的边)
3.重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。

图示如下

原始图
对这个图的顶点进行拓扑排序,过程如下:
首先,我们发现V6和v1是没有前驱的,所以我们就随机选去一个输出,我们先输出V6,删除和V6有关的边,得到如下图结果:
第一步
然后,我们继续寻找没有前驱的顶点,发现V1没有前驱,所以输出V1,删除和V1有关的边,得到下图的结果:
第二步
然后,我们又发现V4和V3都是没有前驱的,那么我们就随机选取一个顶点输出(具体看你实现的算法和图存储结构),我们输出V4,得到如下图结果:

第三步

然后,我们输出没有前驱的顶点V3,得到如下结果:
第四步
然后,我们分别输出V5和V2,最后全部顶点输出完成,该图的一个拓扑序列为:
v6–>v1—->v4—>v3—>v5—>v2

即:
1.从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
2.从图中删除该顶点和所有以它为起点的有向边。
3.重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。若当前图中不存在无前驱的顶点说明有向图中必存在环。

扫描二维码关注公众号,回复: 9225101 查看本文章

AC代码(可做简陋模板使用)

事实上这个代码写的有点冗长,但好处是易于理解,还有一种复杂度更低的形式,以后再写吧。
把函数fuhuan部分去掉即可作为一个简陋的模板使用。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=1e6+7;
const int INF=0x3f3f3f3f;
int t[1007][1007];//存图
int du[1007];//记录入度
void init()//初始化
{
	for(int i = 0 ; i < 1007 ; i ++)
	{
		for(int j = 0 ; j < 1007 ; j++ )
		{
			t[i][j] = 0 ;
			du[i] = 0;
		}
	}
}
bool fuhuan(int a)//判断是否有负环
{
	bool w;
	int in[1007];
	for(int i = 1 ; i <= a ; i++  )
	{
		in[i] = du[i];
	}
	for(int i = 1 ; i <= a ; i ++)
	{
		w = 0 ;
		for(int j = 1 ; j <= a ; j ++ )
		{
			if(in[j] == 0)
			{
				w = 1;
				in[j] = -1;
				for(int l = 1 ; l <= a ; l++)
				{
					if( t[j][l] ) in[l]--;
				}
				break;
			}
		}
		if( !w ) return 0;
	}
	if( w ) return 1;
	return 0;
}
int main()
{
	int n,m;
	while(cin >> n >> m)
	{ 
		init();
		int u,v;
		for(int i = 1 ; i <= m ; i++)//输入图
		{
			cin >> u >> v ;
			if( ! t[u][v] )//标记并记录入度
			{
				t [u][v] = 1 ;
				du [v] ++;
			}
		}
		if( ! fuhuan(n) ) 
		{
			cout << "IMPOSABLE" << endl ;
			continue;
		}
		else    //以下即为拓扑排序部分
		{
			int ans = 0,k;
			for(int i = 1 ; i <= n ; i ++)
			{
				for(int j = 1 ; j <= n ; j++)
				{
					if( ! du[j] )
					{
						ans = j ;
						du[j] -- ;
						k = j;
						break;
					}
				}
	
				for(int j = 1 ; j <= n ; j++)
				{
					if(t[k][j] == 1)
					{
						t[k][j] = 0 ;
						du[j] -- ;
					}
				}
				if( i != n ) cout << k <<" ";
				else cout << k << endl;
			}
		}
		
	}
	
	return 0;
}

PS

感谢阳神的课件!!
这里是课件
orz 跟着大佬的步伐,前进!!
同时,这也是我首次正式尝试松散一些的代码风格,之前一直喜欢写的很紧凑,这样虽然省时省力但阅读起来比较不友好,为了将来队友阅读的方便,以后慢慢开始使用这种码风吧。

发布了11 篇原创文章 · 获赞 2 · 访问量 4014

猜你喜欢

转载自blog.csdn.net/qq_39657434/article/details/104162267
今日推荐