美文网首页
数据结构与算法之图(三)图的拓扑排序

数据结构与算法之图(三)图的拓扑排序

作者: kakaxicm | 来源:发表于2018-06-18 12:15 被阅读0次

    引言

    工程中尝尝氛围很多步骤完成,有些步骤可以同步进行,而有些步骤需要某些步骤完成后才能进行,如何安排它们的流程是项目实施要考虑的重要问题,这就是拓扑排序问题。拓扑排序,它可以决定哪些子工程必须要先执行,哪些子工程要在某些工程执行后才可以执行。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
    一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行(对于数据流来说就是死循环)。在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

    拓扑排序思想

    1.在有向图中选一个没有前驱的顶点并且输出;
    2.从图中删除该顶点和所有以它为尾的弧(白话就是:删除所有和它有关的边);
    3.重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。
    拓扑排序有点像抽丝剥茧,每一次遍历都会去掉最外层的"壳",抽完为止。
    图解
    1.针对下图:



    有入度0的顶点V1和V6,先把他们放入集合S=[V1,V6],这里随机取出V6遍历,S=[V1]。
    2.V6遍历完,去掉和V4、V5的边(入度减1),图结构如下:



    3.从集合中取出V1,删除边,V2、V3、V4入度减一后,V4和V3入度为0,S=[V3,V4],图结构如下:

    4.从集合中取出V4或者V3(取哪一个看具体算法),这里取V4,V5入度为减一不为0,S为[V3],图结构为:

    5,从集合中取出V3,V2、V5入度为0,S=[V2,V5],图结构如下:
    image
    6.继续输出集合中蒸鱼顶点,最后的该图的一个拓扑排序结果为:

    v6—>v1—>v4—>v3—>v5—>v2。
    注意:拓扑排序仅反应顶点间的层序关系,输出结果不唯一。

    代码实现

    代码实现,我们引入一个集合(栈或者队列均可),存放未遍历的入度为0的顶点;引入计数变量检测图是否遍历完。具体实现如下:

    package graphic;
    
    import queue.Queue;
    
    /**
     * Created by chenming on 2018/6/16
     * 拓扑排序
     */
    public class TopologySort {
        private class VNode {
            int value;//值
            int inDegree;//入度
        }
    
        private int[][] map;//邻接矩阵
        private VNode[] nodes;
    
        public TopologySort(int[][] map) {
            this.map = map;
            nodes = new VNode[map.length];
            for (int i = 0; i < map.length; i++) {
                //初始化入度
                VNode node = new VNode();
                node.value = i;
                node.inDegree = getIndegree(i);
                nodes[i] = node;
            }
        }
    
        /**
         * 从邻接矩阵中得到顶点入度
         *
         * @param index
         * @return
         */
        private int getIndegree(int index) {
            int in = 0;
            for (int i = 0; i < map.length; i++) {
                if (0 < map[i][index] && map[i][index] < Integer.MAX_VALUE) {
                    in++;
                }
            }
            return in;
        }
    
        /**
         * 有向图的拓扑排序,,思路如下:
         * 1,类似广度优先遍历,引入队列保存入度为0的未遍历节点
         * 2.每去掉一个顶点,则去掉该顶点的边,即它所有邻接顶点的顶点入度-1,如果为0则加入队列
         * 3.循环执行步骤2,直到队列为空
         * 4.循环结束检测是否遍历完整,如果没有遍历完全,则表示有回环
         */
        public void topologySort() {
            Queue<Integer> queue = new Queue<>();//保存入度为0的索引
            int numb = map.length;
            int count = 0;//校验用
            //收集入度为0的顶点
            for (int i = 0; i < numb; i++) {
                if (nodes[i].inDegree == 0) {
                    queue.enquene(i);
                }
            }
    
            while (!queue.isEmpty()) {
                int index = queue.dequeue();
                System.out.println("拓扑排序:" + index);
                count++;
                //遍历完"删除"顶点index的所有边,即减少它邻接顶点的入度
                for (int i = 0; i < numb; i++){
                    if(map[index][i] > 0 && map[index][i] < Integer.MAX_VALUE){
                        if(--nodes[i].inDegree == 0){
                            //入度减一后,加入队列
                            queue.enquene(i);
                        }
    
                    }
                }
    
            }
    
            if(count != numb){
                throw new RuntimeException("图中存在回环");
            }
    
        }
    }
    

    测试图用例:


    拓扑排序用例.png

    测试代码:

       @Test
        public void testTopol() {
            int INF = Integer.MAX_VALUE;
            int[][] map = {
                    {0, INF, INF, INF, 1, 1, INF, INF, INF, INF, INF, 1, INF, INF},//0
                    {INF, 0, 1, INF, 1, INF, INF, INF, 1, INF, INF, INF, INF, INF},//1
                    {INF, INF, 0, INF, INF, 1, 1, INF, INF, 1, INF, INF, INF, INF},//2
                    {INF, INF, 1, 0, INF, INF, INF, INF, INF, INF, INF, INF, INF, 1},//3
                    {INF, INF, INF, INF, 0, INF, INF, 1, INF, INF, INF, INF, INF, INF},//4
                    {INF, INF, INF, INF, INF, 0, INF, INF, 1, INF, INF, INF, 1, INF},//5
                    {INF, INF, INF, INF, INF, 1, 0, INF, INF, INF, INF, INF, INF, INF},//6
                    {INF, INF, INF, INF, INF, INF, INF, 0, INF, INF, INF, INF, INF, INF},//7
                    {INF, INF, INF, INF, INF, INF, INF, 1, 0, INF, INF, INF, INF, INF},//8
                    {INF, INF, INF, INF, INF, INF, INF, INF, INF, 0, 1, 1, INF, INF},//9
                    {INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 0, INF, INF, 1},//10
                    {INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 0, INF, INF},//11
                    {INF, INF, INF, INF, INF, INF, INF, INF, INF, 1, INF, INF, 0, INF},//12
                    {INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 0},//13
            };
            TopologySort ts = new TopologySort(map);
            ts.topologySort();
        }
    

    输出:

    拓扑排序:0
    拓扑排序:1
    拓扑排序:3
    拓扑排序:4
    拓扑排序:2
    拓扑排序:6
    拓扑排序:5
    拓扑排序:8
    拓扑排序:12
    拓扑排序:7
    拓扑排序:9
    拓扑排序:10
    拓扑排序:11
    拓扑排序:13
    

    完整代码地址:数据结构与算法学习JAVA描述GayHub地址

    相关文章

      网友评论

          本文标题:数据结构与算法之图(三)图的拓扑排序

          本文链接:https://www.haomeiwen.com/subject/dhcneftx.html