Java拓扑排序

来源:互联网 发布:免费字体下载软件 编辑:程序博客网 时间:2024/06/06 01:39

  拓扑排序是对有向无圈图的顶点的一种排序,使得如果存在一条从Vi到Vj的路径,那么在排序中Vj就在Vi的后面。

下面是课程先修结构(course prerequisite structure)的无圈图。有向边(v,w)表明课程v必须在课程w选修前修完。这些课程的拓扑排序是不破坏课程结构要求的任意的课程序列。


显然,如果图含有圈,那么拓扑排序是不可能的,因为对于圈上的两个顶点v和w,v先于w同时w又先于v。此外,拓扑排序不必是唯一的;任何合理的排序都是可以的。

下图中:v1,v2,v5,v4,v3,v7,v6和v1,v2,v5,v4,v7,v3,v6都是拓扑排序。


一个简单的求拓扑排序的算法是先找出任意一个没有入边的顶点。然后显示出该顶点,并将它及其边一起从图中删除。然后,我们对图的其余部分同样应用这样的方法处理。

我们把顶点v的入度(indegree)定义为边(u,v)的条数。计算图中所有顶点的入度。假设每一个顶点的入度被存储且图被读入一个邻接表中,生成拓扑排序的伪代码:

    void topsort() throws CycleFoundException{    for(int counter=0;counter<NUM_VERTICES;counter++){    Vertex v = findNewVertexOfIndegreeZero();    if(v==null){    throw new CycleFoundException();    }    v.topNum = counter;        for each Vertex w adjacent to v      w.indegree--;    }    }
方法findNewVertexOfIndegreeZero扫描数组,寻找一个尚未被分配拓扑编号的入度为0的顶点。如果这样的顶点不存在,它则返回null,这就说明,该图有圈。

  因为findNewVertexOfIndegreeZero方法是对顶点数组的一个简单的顺序扫描,所以每次对它的调用都花费O(|V|)时间。由于有|V|次这样的调用,因此该算法的运行时间为O(|v|2)。

  我们可以通过将所有(未分配拓扑编号)的入度为0的顶点放在一个特殊的盒子中而消除多于的循环扫描。此时findNewVertexOfIndegreeZero方法返回(并删除)的是该盒子中的任意顶点。当我们降低它的邻接顶点的入度时,检查每一个顶点并在它的入度降为0时把它放入盒子中。

  为了实现这个盒子,我们可以使用一个栈或一个队列。首先,对每个顶点计算它的入度。然后,将所有入度为0的顶点放入一个初始为空的队列中。当队列不空时,删除一个顶点v,并将与v邻接的所有顶点的入度均减1。只要一个顶点的入度降为0,就把该顶点放入队列中。此时,拓扑排序就是顶点出队的顺序。下图显示每一阶段之后的状态:


实施拓扑排序的伪代码如下,和前面一样,我们将加速图已经被读到一个邻接表中且入度被计算并和顶点一起被存储。我们还假设每个顶点有一个域,叫做topNum,其中存放的是拓扑编号。

    void topsort() throws CycleFoundException{    Queue<Vertex> q = new Queue<Vertex>();    int counter = 0;    for each Vertex w       if(v.indegree==0){      q.enqueue(v);      }            while(!q.isEmpty()){    Vertex v = q.dequeue();    v.topNum = ++counter;    for each Vertex w adjacent to v      if(--w.indegree==0){      q.enqueue(w);      }    }    if(counter!=NUM_VERTICES){    throw new CycleFoundException();    }        }    
如果使用邻接表,那么执行这个算法所用的时间为O(|E|+|V|)。当认识到for循环体对每条边顶多执行一次时,这个结果是明显的。队列操作对每个顶点最多进行一次,而初始化各补花费的时间也和图的大小成正比。





0 0
原创粉丝点击