• 有向图 拓扑排序 文件依赖下的编译顺序该如何确定?


    有2种常用方式

    1.kahn算法

    2.基于深度优先的逆后序

    都需要有向图中无环,否则依赖关系的顺序可能产生问题

    若有 文件 a.c b.c c.c d.c 他们之间的依赖关系是

    a文件被b文件依赖,b文件被c文件依赖,b文件被d文件依赖
    那么哪个文件被先编译? 被依赖的最多的那个文件(a或d)应该被先编译。 如何得到正确的编译顺序?

    a.c -> b.c -> c.c

    d.c ->

    1.kahn

    //拓扑排序
    //无环有向图 是 拓扑排序的前提
    
    //拓扑排序后,顶点所依赖的前驱节点必定都先出现再他前面(这种序列叫做 拓扑序列)
    //只要满足上述条件的排序输出,都是拓扑序列(所以一个图往往会有多个拓扑序)(这种排序过程叫做 拓扑排序)
    //
    //拓扑排序再生活中的应用,比如任务的依赖关系,被依赖的基层任务应该先完成,比如穿几件衣服,一定是先把穿在里面的衣服穿了以后,再穿外面的外套
    public class TopologicalKahn {
        Digraph dg;
        List<Integer> order;                //顶点的拓扑顺序
        int[] inDegree;
    
        public TopologicalKahn(Digraph dg) {
            this.dg = dg;
            //Kahn 算法,获取拓扑排序
            //1.统计所有顶点入度
            //2.将入度为0的作为起点,加入到queue
            //3.从queue中取出顶点,直到队列为空,将该顶点所指向的顶点入度-1,如果入度=0,则加入队列,循环第三步
    
            //存在环时:输出的顶点数量少于有向图中的顶点数量,或到最后结束循环时,还存在有入度不为0的顶点
    
            order = new LinkedList<>();
    
            //1.
            inDegree = new int[dg.v()];
            for (int u = 0; u < dg.v(); u++) {
                for (int w : dg.adj(u)) {
                    inDegree[w]++;
                }
            }
    
            //2.队列中按 成为0入度 的顺序加入顶点
            Queue<Integer> queue = new LinkedList<>();
            for (int u = 0; u < dg.v(); u++)
                if (inDegree[u] == 0)
                    queue.add(u);
    
            //3.
            while (!queue.isEmpty()) {
                int u = queue.remove();
                order.add(u);
                for (int w : dg.adj(u)) {
                    inDegree[w]--;
                    if (inDegree[w] == 0)   //若入度不为0,说明还有其他指向该点的边
                        queue.add(w);
                }
            }
    
            //4.若最终输出的入度为0的顶点个数小于 原来有向图中顶点个数,说明存在环
            if(order.size() < dg.v())
                order = null;
    
            this.dg = null;
        }
    
        public boolean isDAG() {                //是有向无环图吗?
            return null != order;
        }
    
        public Iterable<Integer> getOrder() {
            return order;
        }
    
        public static void main(String[] args) {
            List<String> books = new LinkedList<>();
            books.add("a.c");
            books.add("b.c");
            books.add("c.c");
            books.add("d.c");
            SymblowDigraph sd = new SymblowDigraph(books);
            sd.addEdge("a.c", "b.c");
            sd.addEdge("b.c", "c.c");
            sd.addEdge("d.c", "b.c");
    
    
            //编译时 a文件被b文件依赖,b文件被c文件依赖,b文件被d文件依赖
            //那么哪个文件被先编译? 被依赖的最多的那个文件(a或d)应该被先编译。 如何得到正确的编译顺序?
            // a.c -> b.c -> c.c
            // d.c ->
    
            Digraph dg = sd.getGraph();
            TopologicalKahn top = new TopologicalKahn(dg);
            if (top.isDAG()) {
                System.out.println("拓扑序列. 文件优先编译顺序(被依赖深度高的先被编译)");
                for (int i : top.getOrder()) {
                    System.out.println(" " + sd.getSymblow(i));
                }
            }
        }
    }

    输出

    拓扑序列. 文件优先编译顺序(被依赖深度高的先被编译)
     a.c
     d.c
     b.c
     c.c

    2.深度优先+逆后序

    将原有向图,用深度优先,求得顶点的逆后序即可

    若原图为 a->b->c<-d

    先看深度优先的调用堆栈(起点从顶点a遍历到d,若碰到已经访问过的顶点,则跳过不访问,访问后需要将该顶点标记为已访问)

    a

     b

      c

      c

     b

    a

    d

    d

    调用堆栈每深入一次,前面加一个空格

    在说后序:指的是,从调用堆栈退出时将顶点放入队列

    那上面的例子就是

    a

     b

      c

      c - 1

     b - 2 

    a - 3

    d

    d - 4

    得到的序列为 cbad

    然后将该顺序求逆向输出,得到  dabc

    dabc就是一条符合原有向图的拓扑排序

      

    //拓扑排序
    //无环有向图 是 拓扑排序的前提
    
    //拓扑排序后,顶点所依赖的前驱节点必定都先出现再他前面(这种序列叫做 拓扑序列)
    //只要满足上述条件的排序输出,都是拓扑序列(所以一个图往往会有多个拓扑序)(这种排序过程叫做 拓扑排序)
    //
    //拓扑排序再生活中的应用,比如任务的依赖关系,被依赖的基层任务应该先完成,比如穿几件衣服,一定是先把穿在里面的衣服穿了以后,再穿外面的外套
    public class Topological {
        Digraph dg;
        DirectedCycle dc;
        DFOrder dfo;                 //逆有向图
        Iterable<Integer> order;                //顶点的拓扑顺序
    
        public Topological(Digraph dg) {
            this.dg = dg;
            dc = new DirectedCycle(dg);
            if (!dc.hasCycle()) {               //若存在环,则不计算拓扑排序
                dfo = new DFOrder(dg);
                order = dfo.reversePost();      //拓扑排序会用到深度优先
            }
            this.dg = null;
        }
    
        public boolean isDAG() {                //是有向无环图吗?
            return null != order;
        }
    
        public Iterable<Integer> getOrder() {
            return order;
        }
    
        public static void main(String[] args) {
            List<String> books = new LinkedList<>();
            books.add("小鱼");
            books.add("泥巴");
            books.add("赵家六");
            books.add("虾子");
            books.add("大鱼");
            books.add("牧羊犬");
            books.add("饲料");
            books.add("廉价劳动力");
            SymblowDigraph sd = new SymblowDigraph(books);
            sd.addEdge("泥巴", "虾子");
            sd.addEdge("虾子", "小鱼");
            sd.addEdge("饲料", "小鱼");
            sd.addEdge("小鱼", "大鱼");
            sd.addEdge("小鱼", "廉价劳动力");
            sd.addEdge("小鱼", "牧羊犬");
            sd.addEdge("大鱼", "牧羊犬");
            sd.addEdge("廉价劳动力", "牧羊犬");
            sd.addEdge("大鱼", "赵家六");
            sd.addEdge("牧羊犬", "赵家六");
            sd.addEdge("廉价劳动力", "赵家六");
            //泥巴被虾子吃,虾子被小鱼吃,饲料被小鱼吃,小鱼被大鱼吃,小鱼被牧羊犬吃,小鱼被廉价劳动力吃,大鱼被牧羊犬吃,大鱼被赵家六吃,廉价劳动力被牧羊犬吃,牧羊犬被赵家六吃,廉价劳动力被赵家六吃
            //那么整个食物链的低端(被依赖)到顶端的关系是?
    
            Digraph dg = sd.getGraph();
            Topological top = new Topological(dg);
            if (top.isDAG()) {
                System.out.println("拓扑序列. 被依赖的靠近顶行(并行被依赖的话,前后顺序不重要)");
                for (int i : top.getOrder()) {
                    System.out.println(" " + sd.getSymblow(i));
                }
            }
        }
    }

    产生逆后序

    //基于深度优先的顶点排序
    public class DFOrder {
        Digraph dg;
        boolean[] marked;
        Queue<Integer> pre;             //前序
        Queue<Integer> post;            //后序
        Stack<Integer> reversePost;     //逆后序
    
        public DFOrder(Digraph dg) {
            this.dg = dg;
            marked = new boolean[dg.v()];
            pre = new ArrayDeque<>();
            post = new ArrayDeque<>();
            reversePost = new Stack<>();
            for (int u = 0; u < dg.v(); u++) {
                if (!marked[u]) {
                    dfs(u);
                }
            }
            this.dg = null;
        }
    
        private void dfs(int u) {
            pre.add(u);
            marked[u] = true;
            for (int v : dg.adj(u)) {
                if (!marked[v])
                    dfs(v);
            }
            post.add(u);
            reversePost.add(u);
        }
    
        public Iterable<Integer> pre() {
            return pre;
        }
    
        public Iterable<Integer> post() {
            return post;
        }
    
        public Iterable<Integer> reversePost() {
            List<Integer> rpList = new ArrayList<>();
            while(reversePost.size() > 0){
                rpList.add(reversePost.pop());
            }
            return rpList;
        }
    
        public static void main(String[] args) {
        }
    }

    输出:

    拓扑序列. 被依赖的靠近顶行(并行被依赖的话,前后顺序不重要)
     饲料
     泥巴
     虾子
     小鱼
     大鱼
     廉价劳动力
     牧羊犬
     赵家六

    2.2. 另一种基于深度优先逆后序的写法:

    (可能会直白好理解一些,没有封装太多,专注在逻辑上)

    //拓扑排序
    //无环有向图 是 拓扑排序的前提
    
    //拓扑排序后,顶点所依赖的前驱节点必定都先出现再他前面(这种序列叫做 拓扑序列)
    //只要满足上述条件的排序输出,都是拓扑序列(所以一个图往往会有多个拓扑序)(这种排序过程叫做 拓扑排序)
    //
    //拓扑排序再生活中的应用,比如任务的依赖关系,被依赖的基层任务应该先完成,比如穿几件衣服,一定是先把穿在里面的衣服穿了以后,再穿外面的外套
    public class TopologicalDFSAndCyc {
        Digraph dg;
        Stack<Integer> reversePostorder;  //顶点的逆后序
        LinkedList<Integer> order;        //顶点的拓扑顺序
        boolean[] inStack;
        boolean[] marked;
    
        public TopologicalDFSAndCyc(Digraph dg) { //拓扑排序会用到深度优先的逆后序
            this.dg = dg;
            inStack = new boolean[dg.v()];
            marked = new boolean[dg.v()];
            reversePostorder = new Stack<>();    //看是否存在环(dfs调用顺序的堆栈上出现了重复,则说明存在环,那么该引用赋为null)
    
            for (int v = 0; v < dg.v(); v++) {
                if (!marked[v])
                    dfs(v);
            }
    
            if (reversePostorder != null) {
                order = new LinkedList<>();
                while (!reversePostorder.isEmpty())
                    order.add(reversePostorder.pop()); //将逆后序输出为正向
            }
    
            this.dg = null;
        }
    
        private void dfs(int v) {
            inStack[v] = true;
            marked[v] = true;
            for (int u : dg.adj(v)) {
                if (reversePostorder == null)   //已经检测到环存在了,跳出循环
                    break;
                if (inStack[u]) {               //dfs调用轨迹上存在相同节点,说明存在环
                    reversePostorder = null;
                } else if (!marked[u]) {
                    dfs(u);                     //对未访问过的顶点访问
                }
            }
            if (reversePostorder != null)       //还未检测到环,则加入当前顶点到逆后序
                reversePostorder.push(v);
            inStack[v] = false;
        }
    
        public boolean isDAG() {                 //是有向无环图吗?(没有环存在吗?)
            return null != order;
        }
    
        public Iterable<Integer> getOrder() {
            return order;
        }
    
        public static void main(String[] args) {
            List<String> books = new LinkedList<>();
            books.add("a.c");
            books.add("b.c");
            books.add("c.c");
            books.add("d.c");
            SymblowDigraph sd = new SymblowDigraph(books);
            sd.addEdge("a.c", "b.c");
            sd.addEdge("b.c", "c.c");
            sd.addEdge("d.c", "b.c");
    
            //编译时 a文件被b文件依赖,b文件被c文件依赖,b文件被d文件依赖
            //那么哪个文件被先编译? 被依赖的最多的那个文件(a或d)应该被先编译。 如何得到正确的编译顺序?
            // a.c -> b.c -> c.c
            // d.c ->
    
            //1.
            Digraph dg = sd.getGraph();
            TopologicalDFSAndCyc top = new TopologicalDFSAndCyc(dg);
            if (top.isDAG()) {
                System.out.println("拓扑序列. 被依赖深度高的靠近顶行");
                for (int i : top.getOrder()) {
                    System.out.println(" " + sd.getSymblow(i));
                }
            }
    
            //2.加入一个环,看输出
            sd.addEdge("c.c", "a.c");
            dg = sd.getGraph();
            top = new TopologicalDFSAndCyc(dg);
            System.out.println("top.isDAG() " + top.isDAG());
            if (top.isDAG()) {
                System.out.println("拓扑序列. 被依赖深度高的靠近顶行");
                for (int i : top.getOrder()) {
                    System.out.println(" " + sd.getSymblow(i));
                }
            }
        }
    }

    输出:

    拓扑序列. 被依赖深度高的靠近顶行
     d.c
     a.c
     b.c
     c.c
    top.isDAG() false

    关于深度优先得到拓扑排序的方式有2种

    1. 将原图求逆图,然后用深度优先,输出后序

    2.不将原图求逆图,用深度优先,输出逆后序

    举个例子

    原图 a->b->c

    方法1.

    从a遍历到c

    a<-b<-c

    a

    a - 1

    b

    b - 2

    c

    c - 3

    输出序列为 abc

    方法2.

    从a遍历到c

    a

     b

      c

      c - 3

     b - 2 

    a - 1

    输出序列 abc

    所以这两种方式是一样的

    从方法2的角度来看方法1,只不过是在每层深度遍历的时候,其逆序(堆栈),被先准备好的逆图搞定了后序的输出顺序

    从方法1的角度来看方法2,不过是在每次深度遍历的时候,用堆栈实现了方法1用逆图实现的后序的逆

  • 相关阅读:
    网站
    世上本无事,庸人自扰之
    mac系招聘BBS
    新浪微博语录帝摘录
    dwz jui
    cheap vps
    facebook的开发标准
    rails的一些插件
    租房宝
    在Z10上用3G
  • 原文地址:https://www.cnblogs.com/cyy12/p/12031469.html
Copyright © 2020-2023  润新知