图论--AOV网与拓扑排序

AOV网

AOV网,英文(Activity On Vertex Network),是一种用顶点表示活动,有向边表示活动的先后关系的有向无环图。例如:某些课程,是一些课程学习的先决条件,”课程学习“ 这个活动构成严格偏序。
很明显AOV网中不应该有环,因为这样意味着一个活动进行的先决条件是自己,这样就出现了矛盾。
在有向无环图中,只有出边,没有入边(即入度为0 出度不为0)的顶点叫源点,只有入边,没有出边的点叫 汇点,容易证明有向无环图中一定存在源点和汇点,一会儿会用到这两个概念。

拓扑排序

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

这是百度百科对拓扑排序的定义,注意到 “拓扑排序是由某个集合上的一个偏序关系得到集合上的一个全序”。也就是说构成偏序的不仅仅可以是活动的先后关系,还可以是别的,比如 某些问题中的 “从属关系”,“大小关系”…。只要能表示为一个有向无环图的关系,都可以使用拓扑排序得到一个全序。

拓扑排序的算法流程

  1. 找到一个入度为0的结点(即一个源点),将结点插入队列(我习惯用队列,栈也可以,下面用队列做例子)保存在答案中。
  2. 在原图中删掉刚刚的源点, 和这个结点所有的出边,更新该结点的后继结点的入度。
  3. 重复1 和 2 并保存,同时记录保存结点的个数,直到原图中没有入度为0的结点。
  4. 检查保存结点的个数,如果小于结点的总数,则输出有环的信息,原图并非有向无环图,否则输出保存的答案结点序列。
int queue[MAXN] = {0}, head = 0, tail = 0;    //队列,头尾指针 
int ans[MAXN] = {0}, cnt = 0;
for(int i=1;i<=N;i++)      //找初始的源点 
{
	if(in[i]==0)
	{
		tail++;
		queue[tail] = i;
	}
}
do
{
	head++;
	int now = queue[head];
	ans[++cnt] = now;         //保存答案 
	for(int i=1;i<=N;i++)
	{
		if(map[now][i]==1&&i!=now)
		{
			map[now][i] = 0;     //删掉这条边 
			in[i]--;             //更新入度 
			if(in[i]==0)
			{
				tail++;
				queue[tail] = i;
			}
		}
	}
}while(head

由于前面已经说明了,有向无环图必然存在源点,若结束算法时,没有扫描到所有顶点,说明一部分结点构成的子图没有源点,则原图不是有向无环图。
由上面的流程可知,拓扑排序得到的序列可能不是唯一的,因为同一时间可能有多个入度为0的顶点,此时保存的顺序,如果不进行规定的话实际上是任意的。这衍生出了关于拓扑排序的很多问题。

关于拓扑排序的基本问题

  1. 求任意一个拓扑序列 / 求字典序最小的拓扑序列

前一个问题就是刚才写的正常流程,关键是求字典序最小的拓扑序列,这就要定义插入队列的优先级,可以用 C++ 的 优先队列priority_queue,也可以在了解其原理(堆排序)后手写优先队列,以结点编号为优先级。
代码:

    for(int i=1;i<=N;i++)        //找源点
    {
    	if(du[i]==0)          
	      	Put(i);             //小根堆的插入函数,具体实现请学习 “堆”
    }
   	while(Hsize>0)      //当队列非空
    {
    	int now = Heap[1];
    	printf("%d", now);
	   	cnt++;
	   	if(cnt!=N)
    	    printf(" ");
	   	Delete();                   //小根堆的删除函数
	   	for(int i=1;i<=N;i++)        //遍历所有结点寻找now的后继
	   	{
	   		if(map[now][i]==1)       //如果i是now的后继
    		{
     			map[now][i] = 0;       //删掉这条边
     			du[i]--;               //更新入度  
    			if(du[i]==0)          //入度为0则入队
    				Put(i);
    		}
    	}
   	}

上面的代码把 “堆” 的部分去掉就是正常的拓扑排序。

  1. 求所有拓扑序列中,位置固定的元素。

在某些问题中,我们需要确定某个元素的排名。由于拓扑序列的不唯一,一些元素无法确定排名,一些元素可以确定一个唯一的排名,即这个元素在所有拓扑序列中的位置相同。
判断方法是:一个元素的名次确定,当且仅当同一时间只有该结点入度为0,且比该结点先入队的结点与该结点均可比。
理由:比确定排名结点更早入队的结点需要和该结点可比,以保证他们严格“大于” 该结点,如果同一时间有一个以上的入度为0的结点,容易证明这两个结点一定不可比,而且这些结点的入队顺序可以不同,所以这种情况是不会产生名次确定的结点的。

相关问题

拓扑排序模板: 洛谷P1113 杂务.

Floyed+拓扑排序: 洛谷P2419 USACO08JANCow Contest S.

拓扑排序+思维(NOIP普及组考试题): 洛谷P1983 车站分级.

真·拓扑排序 : 洛谷P1347 排序.

你可能感兴趣的:(图论)