精华内容
下载资源
问答
  • 对有向无环图中具有长度约束的最大不相交路径问题进行研究,该问题是求解图中两点间路径长度为 k的最大不相交路径。为了对该问题进行求解,提出了贪婪搜索算法(GP,greedy path),该算法先将一个有向无环图转化为一棵...
  • 有向无环图(DAG)的实现。 该实现是快速且线程安全的。 它可以防止添加循环或重复,从而始终保持有效的DAG。 该实现缓存后代和祖先,以加快后续调用的速度。 快速开始 跑步: package main import ( "fmt" ...
  • 矢量坎 一个有向无环图看板 大小和方向。
  • 具有边缘标记的简单DAG(有向无环图)模块。 安装 $ npm install dagjs 用法 let Dag = require ( 'dagjs' ) ; let dag = new Dag ( ) ; // ... 例子 添加边: let dag = new Dag ( ) ; // add(from, to, tags, ...
  • 达格 有向无环图的Golang实现 总览 这个包...
  • 主要为大家详细介绍了python使用xlsxwriter实现有向无环图到Excel的转换,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 判断给定的图是不是是有向无环图,方法是应用拓扑排序,代码如下
  • NULL 博文链接:https://128kj.iteye.com/blog/1675685
  • 为研究基于有向无环图的支持向量机分类算法以及在故障诊断问题中的应用,考虑到有向无环图的结构运算相当于一个表操作,且分类结果依赖于有向无环图中节点的排列顺序,提出一种分类算法,该算法引入基于类分布的类间...
  • 采用深度优先算法(DFS)遍历有向无环图寻找最优路径,经过优化的深度优先算法,在遍历有向无环图的时候保存路径,并计算路径权值,最总返回最优路径及最有路径的权值
  • 带你了解有向无环图和拓扑排序

    千次阅读 2020-04-09 19:19:42
    在图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。而提到DAG,就差不多会联想到拓扑排序,拓扑排序是指由某个集合上的一个偏序得到该集合上的一个全序的操作。...

    写在前面

    如果觉得有所收获,记得点个关注和点个赞,感谢支持。
    今天遇到有向无环图的一些问题,感觉挺有意思的,而且这些问题的思路特点都差不多,所以想着记录一下。在图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。而提到DAG,就差不多会联想到拓扑排序,拓扑排序是指由某个集合上的一个偏序得到该集合上的一个全序的操作。拓扑排序常用来确定一个依赖关系集中,事物发生的顺序。拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。DAG在区块链中得到很广泛的应用哦。

    概念

    图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V, E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。图按照边的有无方向性分为无向图和有向图。

    图中某个节点与其他节点的直连边条数称为该节点的度。有向图中,指向其他节点的边成为出度,被其他节点指向的边称为入度。如果在有向图中,无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。

    偏序,集合内只有部分元素之间在这个关系下是可以比较的, 比如:比如复数集中并不是所有的数都可以比较大小,那么“大小”就是复数集的一个偏序关系。全序,集合内任何一对元素在在这个关系下都是相互可比较的,比如:有限长度的序列按字典序是全序的。最常见的是单词在字典中是全序的。

    邻接表与邻接矩阵

    邻接表和邻接矩阵是图的两种常用存储表示方式,用于记录图中任意两个顶点之间的连通关系,包括权值。下面我们来实际举例一下,对于图 G=(V, E) 而言,其中 V 表示顶点集合,E 表示边集合。给定了概念之后,对于无向图 graph,图的顶点集合和边集合如下:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    对于有向图 digraph,图的顶点集合和边集合如下:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    邻接表

    无向图 graph 表示
    在这里插入图片描述
    有向图 digraph 表示
    在这里插入图片描述
    若采用邻接表表示,则需要申请 ∣ V ∣ |V| V 个列表,每个列表存储一个顶点出发的所有相邻顶点。如果图 G G G 为有向图,则 ∣ V ∣ |V| V 个列表存储的总顶点个数为 ∣ E ∣ |E| E ;如果图 G G G 为无向图,则 ∣ V ∣ |V| V 个列表存储的总顶点个数为 2 ∣ E ∣ 2|E| 2E (暂不考虑自回路)。因为需要申请大小为 ∣ V ∣ |V| V 的数组来保存节点,对节点分配序号,所以需要申请大小为 ∣ V ∣ |V| V 的额外存储空间,即邻接表方式的存储空间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

    邻接矩阵

    无向图 graph 表示
    在这里插入图片描述
    有向图 digraph 表示
    在这里插入图片描述
    若采用邻接矩阵表示,则需要申请空间大小为 ∣ V ∣ 2 |V|^2 V2 的二维数组,在二位数组中保存每两个顶点之间的连通关系,则无论有向图或无向图,邻接矩阵方式的存储空间复杂度皆为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2) 。若只记录图中顶点是否连通,不记录权值大小,则可以使用一个二进制位来表示二维数组的每个元素,并且根据无向图的特点可知,无向图的邻接矩阵沿对角线对称,所以可以选择记录一半邻接矩阵的形式来节省空间开销。

    根据邻接表和邻接矩阵的结构特性可知,当图为稀疏图、顶点较多,即图结构比较大时,更适宜选择邻接表作为存储结构。当图为稠密图、顶点较少时,或者不需要记录图中边的权值时,使用邻接矩阵作为存储结构较为合适。

    拓扑排序

    拓扑排序:就是一个有向无环图的所有定点的线性序列。如果在图中,有一条从A点到B点的路线,那么在拓扑排序中,点A一定排在点B的前面。这个东西,是比较难理解,再上图说话吧。比如在这个有向无环图中,它用拓扑排序,该怎么进行呢?
    在这里插入图片描述

    • 先找一个起点,很明显,这个起点就1号点了,因为这个点,没有任何其他指向它的路线。如果存在多个这样的点,那么随意输出就可以了,也就是说,可能存在多个拓扑序列。
    • 然后将这个起点删除,并同时删除这个起点发射出去的路线。
    • 重复上面两个步骤,直到这张有向无环图的所有点都被删除干净。

    如果到某个阶段,发现当前图中不存在像1号点这样的起点了,那么这张图就不是有向无环图了。最后,一个完整的拓扑排序就完成了,结果为:1、2、4、3、5。

    代码实现

    为了更方便理解,我们这里直接贴出一道题,根据这道题的实现代码来体会,这道题是完全按照拓扑排序的思路进行解答的。题目如下
    在这里插入图片描述

    解题思路

    • 本题可约化为: 课程安排图是否是 有向无环图(DAG)。即课程间规定了前置条件,但不能构成任何环路,否则课程前置条件将不成立。
    • 思路是通过 拓扑排序 判断此课程安排图是否是 有向无环图(DAG) 。 拓扑排序原理: 对 DAG 的顶点进行排序,使得对每一条有向边 ( u , v ) (u, v) (u,v) ,均有 u u u(在排序记录中)比 v v v 先出现。亦可理解为对某点 v v v 而言,只有当 v v v 的所有源点均出现了, v v v 才能出现。

    算法流程

    • 统计课程安排图中每个节点的入度,生成 入度表 indegrees
    • 借助一个队列 queue,将所有入度为 0 的节点入队。
    • queue 非空时,依次将队首节点出队,在课程安排图中删除此节点 pre
      • 并不是真正从邻接表中删除此节点 pre,而是将此节点对应所有邻接节点 cur 的入度 −1,即 indegrees[cur] -= 1
      • 当入度 −1后邻接节点 cur 的入度为 0,说明 cur 所有的前驱节点已经被 “删除”,此时将 cur 入队。
    • 在每次 pre 出队时,执行 numCourses--
      • 若整个课程安排图是有向无环图(即可以安排),则所有节点一定都入队并出队过,即完成拓扑排序。换个角度说,若课程安排图中存在环,一定有节点的入度始终不为 0
      • 因此,拓扑排序出队次数等于课程个数,返回 numCourses == 0 判断课程是否可以成功安排。

    算法可视化

    在这里插入图片描述

    实现

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] indegrees = new int[numCourses];
        List<List<Integer>> adjacency = new ArrayList<>();
        Queue<Integer> queue = new LinkedList<>();
        for(int i = 0; i < numCourses; i++)
            adjacency.add(new ArrayList<>());
        
        for(int[] cp : prerequisites) {
            indegrees[cp[0]]++;
            adjacency.get(cp[1]).add(cp[0]);
        }
        
        for(int i = 0; i < numCourses; i++)
            if(indegrees[i] == 0) queue.add(i);
        
        while(!queue.isEmpty()) {
            int pre = queue.poll();
            numCourses--;
            for(int cur : adjacency.get(pre))
                if(--indegrees[cur] == 0) queue.add(cur);
        }
        return numCourses == 0;
    }
    
    展开全文
  • 比较、聚合和聚类有向无环图 (DAG)。 纸张代码: Eric Malmi、Nikolaj Tatti 和 Aristides Gionis,“超越排名:比较有向无环图”。 在数据挖掘和知识发现中,2015 年。 主要文件 dag_dist.m DAG 的距离度量。 ...
  • 图论算法——环和有向无环图

    万次阅读 多人点赞 2019-05-20 18:21:06
    环和有向无环图 在有向图相关的实际应用中,有向环特别重要。一幅图可能含有大量的环,通常,我们一般只关注它们是否存在。 调度问题 给定一组任务并安排它们的执行顺序,限制条件为这些任务的执行方法、起始时间...

    引言

    在有向图相关的实际应用中,有向环特别重要。一幅图可能含有大量的环,通常,我们一般只关注它们是否存在。

    有关图的概念可参考博文数据结构之图的概述

    在学习环之前,我们一起来学习下调度问题。

    调度问题

    给定一组任务并安排它们的执行顺序,限制条件为这些任务的执行方法、起始时间以及任务的消耗等。最重要的一种限制条件叫做优先级限制,它指定了哪些任务必须在哪些任务之前完成。不同类型的限制条件会产生不同难度的调度问题。

    在这里插入图片描述以一个正在安排课程的大学生为例,有些课程是其他课程的先导课程。如上图所示。

    比如要学习机器学习(Machine Learning),得先学习人工智能(Artificial Intelligence)。

    再假设该同学一次只能选修一门课程,就会遇到下面的问题:

    ** 优先级限制下的调度问题**:在满足限制条件的情况下如何安排并完成所有任务。我们可以画出一张有向图,顶点对应任务(通过数组索引来表示课程),有向边对应优先级顺序。

    在这里插入图片描述

    上图9代表人工智能,11代表机器学习。

    而在有向图中,优先级限制下的调度问题等价于拓扑排序:

    拓扑排序:给定一副有向图,将所有的顶点排序,使得所有的有向边均从排在前面的顶点指向排在后面的顶点。

    在这里插入图片描述
    上图为示例的拓扑排序,所有的边都是向下的。按照这个顺序,该同学可以在满足先导课程限制的条件下选修完所有课程。

    有向图中的环

    如果任务x必须在任务y之前完成:x→y,而y→z,同时z→x。那么就会形成一个环:x→y→z→x。如果一个有优先级限制的问题中存在有向环,那么这个问题肯定是无解的。因此我们需要首先进行有向环检测。

    有向环检测:检测给定的有向图是否包含环,若有环,通常只需找出一个即可。

    有向无环图(DAG):不含环的有向图

    可以转换为检查一副有向图是否为有向无环图。

    在这里插入图片描述

    对课程图进行一些修改,增加一些环如上所示。

    寻找环利用了DFS方法,维护一个递归调用期间已访问的顶点的栈,若(在递归调用期间,通过判断onStack标记数组)两次访问了某个顶点,则说明有环;若DFS递归调用完毕,说明无环

    该递归调用期间只是递归dfs调用它的邻接顶点期间(如果有邻接顶点,它的邻接顶点也会执行同样的过程,所以会越来越深),一旦递归完它的所有邻接顶点,会把该顶点从onStack数组中移除

    我们暂且关注0-5六个顶点。执行图解如下:

    初始化3个数组,分别表示是否已访问、路径上的上一个顶点、是否为递归调用期间栈上的顶点
    在这里插入图片描述
    首先dfs(0),将0标记为已访问,并将0的调用栈标记置为true,然后递归的访问0的邻接点1。观察到1没有访问过,将1的上一个节点置为0
    在这里插入图片描述

    dfs(1),将1标记为已访问,并将1的调用栈标记置为true,因为1无邻接点,dfs(1)调用完毕,并将1的调用栈标记置为false(在图中用空白表示false),回退到dfs(0)

    在这里插入图片描述

    递归调用0的下一个邻接点5,将5的上一节点置为0

    递归调用dfs(5)

    5标记为已访问,并将5的调用栈标记置为true

    在这里插入图片描述递归的访问5的邻接点4。观察到4没有访问过,将其上一节点置为5,然后调用

    dfs(4),将4标记为已访问,并将4的调用栈标记置为true,递归的访问4的邻接点2,观察到2没有访问过

    在这里插入图片描述2上一节点置为4,递归调用
    dfs(2)
    2标记为已访问,并将2的调用栈标记置为true,递归的访问2的邻接点0观察到0已经访问过,且0在调用栈中,因此发现了环。 此时已经得出结果可以返回了,但别急,我们将环的路径存入调用栈中,方便后面输出。

    在这里插入图片描述
    cycle环栈输入顺序为:2,4,5,0

    找到的环为:2→0→5→4→2
    在这里插入图片描述

    检测有向图是否有环代码

    package com.algorithms.graph;
    
    import com.algorithms.stack.Stack;
    
    /**
     * @author yjw
     * @date 2019/5/20/020
     */
    public class DirectedCycle {
        private boolean[] marked;
        private int[] edgeTo;
        /**
         * 有向环中的所有顶点(如果存在)
         */
        private Stack<Integer> cycle;
        /**
         * 保存递归调用期间栈上的所有顶点
         */
        private boolean[] onStack;
    
    
        public DirectedCycle(DiGraph g) {
            marked = new boolean[g.vertexNum()];
            edgeTo = new int[g.vertexNum()];
            onStack = new boolean[g.vertexNum()];
            for (int v = 0; v < g.vertexNum(); v++) {
                if (!marked[v]) {
                    dfs(g, v);
                }
            }
        }
    
        private void dfs(DiGraph g, int v) {
            marked[v] = true;
            onStack[v] = true;
            for (int w : g.adj(v)) {
                if (hasCycle()) {
                    //有环直接退出
                    return;
                } else if (!marked[w]) {
                    edgeTo[w] = v;
                    dfs(g, w);
                } else if (onStack[w]) {
                    //如果w已经在栈中,说明再一次访问到了w,因此此时发现了环
                    cycle = new Stack<>();
                    for (int x = v; x != w; x = edgeTo[x]) {
                        cycle.push(x);
                    }
                    cycle.push(w);
                    cycle.push(v);
                }
            }
            onStack[v] = false;
        }
    
        public boolean hasCycle() {
            return cycle != null;
        }
    
        public Iterable<Integer> cycle(int v) {
            return cycle;
        }
    
        public static void main(String[] args) {
            DiGraph g = new DiGraph(13);
            g.addDiEdges(0, 5, 1);
            g.addDiEdges(2, 3, 0);
            g.addDiEdges(3, 2, 5);
            g.addDiEdges(4, 3, 2);
            g.addDiEdges(5, 4);
            g.addDiEdges(6, 0, 4, 9);
            g.addDiEdges(7, 6, 8);
            g.addDiEdges(8, 7, 9);
            g.addDiEdges(9, 10, 11);
            g.addDiEdges(10, 12);
            g.addDiEdges(11, 4, 12);
            g.addDiEdges(12, 9);
    
            //System.out.println(g);
            DirectedCycle dc = new DirectedCycle(g);
            if (dc.hasCycle()) {
                for (int w : dc.cycle) {
                    System.out.print(w + "->");
                }
            }
    
        }
    }
    
    

    接下来一起学习下有向图的拓扑排序,在学习如何拓扑排序之前,我们先了解下图的前序、后序、以及逆后序遍历。
    因为拓扑排序和上述中某遍历序列有千丝万缕的关系。

    顶点的深度优先顺序

    图的深度优先搜索只会访问每个顶点一次,如果将dfs(int v)的参数顶点v保存在一个数据结构中,
    遍历这个数据结构实际上就能访问图中的所有顶点。

    遍历的顺序取决于该数据结构的性质(栈的先进后出、队列的先进先出),以及是在递归调用之前还是之后存入该数据结构。

    常见的的3种遍历顺序如下:

    • 前序:在递归调用之前将顶点加入队列
    • 后序:在递归调用之后将顶点加入队列
    • 逆后序:在递归调用之后将顶点压入

    在这里插入图片描述我们以上面这个图为例,对该图进行3种遍历:

    在这里插入图片描述prepost是队列,reversePost是栈,红色元素为最近插入的元素。从左到右,可以看到队列是先进先出,而栈是先进后出。

    分析下执行过程,首先调用dfs(0),0的邻接点为{2},在递归调用dfs(2)之前,将0入前序队列;
    调用dfs(2)2的邻接点为{0,1,3},因为0刚才已经访问过,不会递归调用0,因此会继续递归调用1(将它入前序队列),它没有邻接点,递归dfs调用1的邻接点完毕,将1入后序队列;

    然后继续调用2的下一个邻接点3,而3又会导致dfs(4)dfs(5),该两个顶点都没有邻接点,因此很快就可以返回,3的所有邻接点递归调用完毕,到了3 done。后面也是一样,20依次调用完毕。

    有向图的前序、后序遍历代码

    package com.algorithms.graph;
    
    import com.algorithms.queue.Queue;
    import com.algorithms.stack.Stack;
    
    /**
     * 顶点的深度优先顺序
     * @author yjw
     * @date 2019/5/21/021
     */
    public class DepthFirstOrder {
        private boolean[] marked;
        /**
         * 所有顶点的前序序列
         */
        private Queue<Integer> pre;
        /**
         * 所有顶点的后序序列
         */
        private Queue<Integer> post;
        /**
         * 所有顶点的逆后序序列
         */
        private Stack<Integer> reversePost;
    
        public DepthFirstOrder(DiGraph g) {
            pre = new Queue<>();
            post = new Queue<>();
            reversePost = new Stack<>();
            marked = new boolean[g.vertexNum()];
            for (int v = 0; v < g.vertexNum(); v++) {
                if (!marked[v]) {
                    dfs(g,v);
                }
            }
        }
    
    
        private void dfs(DiGraph g, int v) {
            marked[v] = true;
            //前序:递归调用dfs之前将顶点加入队列
            pre.enqueue(v);
    
            for (int w : g.adj(v)) {
                if (!marked[w]) {
                    //递归调用dfs
                    dfs(g, w);
                }
            }
    
            //遍历完所有的邻接点后(递归调用dfs之后)
            //后序:递归调用dfs之后将顶点加入队列
            post.enqueue(v);
            //逆后序:在递归调用dfs之后将顶点压入栈
            reversePost.push(v);
        }
    
        public Iterable<Integer> pre() {
           return pre;
        }
    
        public Iterable<Integer> post() {
            return post;
        }
    
        public Iterable<Integer> reversePost() {
            return reversePost;
        }
    
        public static void main(String[] args) {
            DiGraph g = new DiGraph(6);
            g.addEdge(0,2,2,0,2,1,2,3,3,2,3,4,3,5);
    
            DepthFirstOrder dfo = new DepthFirstOrder(g);
            for (Integer v: dfo.pre()){
                System.out.print(v + " ");
            }
            System.out.println();
            for (Integer v: dfo.post()){
                System.out.print(v + " ");
            }
            System.out.println();
            for (Integer v: dfo.reversePost()){
                System.out.print(v + " ");
            }
            System.out.println();
    
            /**
             * 输出:
             * 0 2 1 3 4 5 
             * 1 4 5 3 2 0 
             * 0 2 3 5 4 1 
             */
        }
    }
    
    

    拓扑排序

    回顾一下拓扑排序的描述

    在这里插入图片描述

    拓扑排序:给定一副有向图,将所有的顶点排序,使得所有的有向边均从排在前面的顶点指向排在后面的顶点。

    在这里插入图片描述
    上图为示例的拓扑排序,所有的边都是向下的。按照这个顺序,可以在满足先导课程限制的条件下选修完所有课程。

    一副有向无环图的拓扑排序即为所有顶点的逆后序排列

    package com.algorithms.graph;
    
    /**
     * 有向无环图的拓扑排序即为所有顶点的逆后序排列
     *
     * @author yjw
     * @date 2019/5/21/021
     */
    public class Topological {
        //顶点的拓扑顺序
        private Iterable<Integer> order;
    
        public Topological(DiGraph g) {
            //首先检测是否有环
            DirectedCycle cycle = new DirectedCycle(g);
            if (!cycle.hasCycle()) {
                DepthFirstOrder dfs = new DepthFirstOrder(g);
                order = dfs.reversePost();
            }
        }
    
        public Iterable<Integer> order() {
            return order;
        }
    
        /**
         * 是否为有向无环图
         *
         * @return
         */
        public boolean isDAG() {
            return order != null;
        }
    
        public static void main(String[] args) {
            DiGraph g = new DiGraph(13);
            g.addDiEdges(0, 1, 5, 6);
            g.addDiEdges(2, 0, 3);
            g.addDiEdges(3, 5);
            g.addDiEdges(5, 4);
            g.addDiEdges(6, 4, 9);
            g.addDiEdges(7, 6);
            g.addDiEdges(8, 7);
            g.addDiEdges(9, 10, 11,12);
            g.addDiEdges(11, 12);
    
            Topological tp = new Topological(g);
            if (tp.isDAG()) {
                for (Integer v : tp.order) {
                    System.out.print(v + " ");
                }
            }
        }
    }
    
    
    展开全文
  • final为最终代码,其他为测试代码,有向无环图进行拓扑排序若不是DAG则输出圈
  • 今天小编就为大家分享一篇python判断图环是否存在的示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • Layout algorithms for visualizing directed acylic graphs
  • 数据调度系统中有向无环图的无环检测 名词解释 DAG,全称:Directed Acyclic Graph,中文:有向无环图 入度:有向图中某点作为图中边的终点的次数之和 出度: 对于有向图来说,顶点的出边条数即为该顶点的出度 调度...

    数据调度系统中有向无环图的无环检测

    名词解释

    • DAG,全称:Directed Acyclic Graph,中文:有向无环图
    • 入度:有向图中某点作为图中边的终点的次数之和
    • 出度: 对于有向图来说,顶点的出边条数即为该顶点的出度

    调度系统中的有向无环图

    数据调度系统中,多个任务之间的依赖关系,用图可以表示,如下图所示,每个顶点表示一个任务,箭头方向表示任务的执行顺序。任何顶点都无法经过边回到该顶点,则该图就是无环图,即为有向无环图(DAG图)。
    图1图1

    那么在有向图中,如果有环的存在,如图示:
    图2图2

    在有环的情况下,任务3的执行需要任务5完成,而5的执行又需要3,4依次执行完成,这样就会造成死循环,此调度任务永远无法成功执行。所以在调度系统中,对于无环的检测就非常重要。

    无环检测

    在调度系统中,检查图是否有环,分两种场景:1. 编辑图的过程中每一步操作都需要对其做无环检测;2. 对已经存在的图进行拓扑排序,检测是否有环。

    编辑时检测

    对于新创建的图来说,每增加一条边,都需要检测,这条边是否导致环的存在
    思路:如图1到图2, 如果要加一条5到3的边,就从3开始遍历后续顶点,如果能回到顶点5的话就证明新加的边导致环的产生;如果不能回到5,证明新添加的边不会导致有环。
    检测代码如下:

     /**
       * 判断增加边(fromVertex --> toVertex)是否合法,需要判断是否符合无环的约束
       *
       * @param fromVertex     起点
       * @param toVertex       终点
       * @param createVertex 是否创建结点
       * @return
       */
      private boolean isLegalAddEdge(Vertex fromVertex, Vertex toVertex, boolean isCreate) {
          if (fromVertex.equals(toVertex)) {
              logger.error("edge fromVertex({}) can't equals toVertex({})",fromVertex,toVertex);
              return false;
          }
          if (!isCreate) {
              if (!hasVertex(fromVertex) || !hasVertex(toVertex)){
                  logger.error("edge fromVertex({}) or toVertex({}) is not in vertices map",fromVertex,toVertex);
                  return false;
              }
          }
          Queue<Vertex> queue = new LinkedList<>();
          queue.add(toVertex);
          int verticesCount = getVerticesCount();
          //如果没有找到fromVertex, 表示无环;
          while (!queue.isEmpty() && verticesCount > 0) {
              verticesCount -= 1;
              Vertex key = queue.poll();
              // 遍历后续顶点
              for (Vertex subsequentVertex : getSubsequentNodes(key)) {
                  if (subsequentVertex.equals(fromVertex)) {
                      return false;
                  }
                  queue.add(subsequentVertex);
              }
          }
          return true;
      }
    

    拓扑排序检测

    有向图的拓扑排序,思路如下:

    1. 遍历图中所有的顶点,将入度为0的顶点入队列(如果多个顶点入度为0,取顶点顺序可能不一样,所以此处排序结果可能是多个)。
    2. 从队列中poll出一个顶点,更新该顶点的邻接点的入度(减1),如果邻接点的入度减1之后等于0,则将该邻接点入队列。
    3. 一直执行第2步,直到队列为空。
    4. 如果无法遍历完所有的结点,则意味着当前的图不是无环图。不存在拓扑排序。

    例如图1 入度出度:

    顶点入度出度
    顶点102
    顶点211
    顶点311
    顶点421
    顶点510

    拓扑排序流程如下:

    在这里插入图片描述

    java实现如下:

      /**
       * 判断是否有环及拓扑排序结果
       *
       * 有向无环图(DAG)才有拓扑(topological)排序
       * 广度优先遍历的主要做法:
       *    1、遍历图中所有的顶点,将入度为0的顶点入队列。
       *    2、从队列中poll出一个顶点,更新该顶点的邻接点的入度(减1),如果邻接点的入度减1之后等于0,则将该邻接点入队列。
       *    3、一直执行第2步,直到队列为空。
       * 如果无法遍历完所有的结点,则意味着当前的图不是有向无环图。不存在拓扑排序。
       *
       *
       * @return key返回的是状态, 如果成功(无环)为true, 失败则有环, value为拓扑排序结果(可能是其中一种)
       */
      private Map.Entry<Boolean, List<Vertex>> topologicalSortImpl() {
        //入度为0的结点队列
        Queue<Vertex> zeroIndegreeVertexQueue = new LinkedList<>();
        //保存结果
        List<Vertex> topoResultList = new ArrayList<>();
        //保存入度不为0的结点
        Map<Vertex, Integer> notZeroIndegreeVertexMap = new HashMap<>();
    
        //扫描所有的顶点,将入度为0的顶点入队列
        for (Map.Entry<Vertex, VertexInfo> vertices : verticesMap.entrySet()) {
          Vertex vertex = vertices.getKey();
          int inDegree = getIndegree(vertex);
    
          if (inDegree == 0) {
            zeroIndegreeVertexQueue.add(vertex);
            topoResultList.add(vertex);
          } else {
            notZeroIndegreeVertexMap.put(vertex, inDegree);
          }
        }
    
        //扫描完后,没有入度为0的结点,说明有环,直接返回
        if(zeroIndegreeVertexQueue.isEmpty()){
          return new AbstractMap.SimpleEntry(false, topoResultList);
        }
    
        //采用topology算法, 删除入度为0的结点和它的关联边
        while (!zeroIndegreeVertexQueue.isEmpty()) {
          Vertex v = zeroIndegreeVertexQueue.poll();
          //得到相邻结点
          Set<Vertex> subsequentNodes = getSubsequentNodes(v);
    
          for (Vertex subsequentVertex : subsequentNodes) {
    
            Integer degree = notZeroIndegreeVertexMap.get(subsequentVertex);
    
            if(--degree == 0){
              topoResultList.add(subsequentVertex);
              zeroIndegreeVertexQueue.add(subsequentVertex);
              notZeroIndegreeVertexMap.remove(subsequentVertex);
            }else{
              notZeroIndegreeVertexMap.put(subsequentVertex, degree);
            }
    
          }
        }
    
        //notZeroIndegreeVertexMap如果为空, 表示没有环
        AbstractMap.SimpleEntry resultMap = new AbstractMap.SimpleEntry(notZeroIndegreeVertexMap.size() == 0 , topoResultList);
        return resultMap;
    
      }
    
    }
    

    输出每个顶点的同时还要删除以它为起点的边。如果图有V个顶点,E条边,则一般该算法的时间复杂度为O(V+E)。这里实现的算法最终key返回的是状态, 如果成功(无环)为true, 失败则有环, 无环时value为拓扑排序结果(可能是其中一种)。

    展开全文
  • 环和有向无环图

    千次阅读 2018-10-31 17:22:49
    文章目录定义有向环检测基于DFS的顶点排序拓扑排序 在和有向图相关的实际应用中,有向环特别的重要。 优先级限制下的调度问题:给定一组需要...优先级限制下的调度问题等价于计算有向无环图中所有顶点的拓扑排序。 ...


    在和有向图相关的实际应用中,有向环特别的重要。

    优先级限制下的调度问题:给定一组需要完成的任务,以及一组关于任务完成先后次序的优先级限制。在满足限制条件的前提下应该如何安排并完成所有任务?

    对于任意一个这样的问题,我们都可以画出一张有向图,其中顶点对应任务,有向边对应优先级顺序。
    优先级限制下的调度问题等价于计算有向无环图中所有顶点的拓扑排序


    定义

    给定一幅有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素。

    拓扑排序是对有向无环图的顶点的一种排序, 使得如果存在一条从v到w的路径,那么在排序中w就出现在v的后面。
    如果图含有环,那么拓扑排序是不可能的。试想有3个正整数,a比b大,b比c大,c比a大,我们无法对abc排序。


    有向环检测

    如果一个有优先级限制的问题中存在有向环,那么这个问题肯定无解。要检查这种错误,需要进行有向环检测,即给定的有向图中包含有向环吗?

    只需要找出一个环即可,而不是所有环。

    基于深度优先搜索来解决这个问题并不困难,因为由系统维护的递归调用的栈表示的正是“当前”正在遍历的有向路径。一旦我们找到了一条边 v->w 且 w 已经存在于栈中,就找到了一个环,因为栈表示的是一条由 w 到 v 的有向路径,而 v->w 刚好补全了这个环。同时,如果没找到这样的边,就意味着这幅有向图是无环的。
    在这里插入图片描述

    /*
     * 寻找有向环
     */
    public class DirectedCycle {
        private boolean[] marked;
        private int[] edgeTo;           // 在找到有向环后,环上的所有顶点可以通过edgeTo[]中的链接得到
        private Stack<Integer> cycle;   // 有向环中的所有顶点(如果存在)
        private boolean[] onStack;      // 保存递归调用的栈上的所有顶点。当找到一条边v->w且w在栈中时,就找到了一个有向环
        
        public DirectedCycle(Digraph G) {
            onStack = new boolean[G.V()];
            edgeTo = new int[G.V()];
            marked = new boolean[G.V()];
            for (int v = 0; v < G.V(); v++)
                if (!marked[v]) 
                	dfs(G, v);
        }
    
        private void dfs(Digraph G, int v) {
            onStack[v] = true; //这个变量是神来之笔。因为有好几棵树,但我只要查我所在的这棵树。所以在递归的时候一路把这棵树标成true,在返回之前再标回false。为下一棵树可以循环再利用做准备。
            marked[v] = true;
            for (int w : G.adj(v))
                if (this.hasCycle()) 
                	return;
                else if (!marked[w]) {
                    edgeTo[w] = v;
                    dfs(G, w);
                } 
                else if (onStack[w]) {    // 我遇到了组织,我们形成了一个环!
                    cycle = new Stack<Integer>();
                    for (int x = v; x != w; x = edgeTo[x])
                        cycle.push(x);
                    cycle.push(w);
                    cycle.push(v);       //v压了两次,第一次作为箭头终点,第二次作为箭头起点
                }
            onStack[v] = false;
        }
    
        public boolean hasCycle() { return cycle != null; }
        public Iterable<Integer> cycle() { return cycle; }
    }
    

    基于DFS的顶点排序

    先关注 “有向图中基于深度优先搜索的顶点排序” 的 DepthFirstOrder 类。
    他的基本思想是DFS正好只会访问每个顶点一次。如果将 dfs() 的参数顶点保存在一个数据结构中,遍历这个数据结构实际上就能访问图中的所有顶点,遍历的顺序取决于这个数据结构的性质以及是在递归调用之前还是之后进行保存。


    顶点的3种排列顺序:
    前序:在递归调用之前将顶点加入队列
    后序:在递归调用之后将顶点加入队列
    逆后序:在递归调用之后将顶点压入栈


    下述算法允许用例用各种顺序遍历DFS经过的所有顶点

    /*
     * 有向图中基于深度优先搜索的顶点排序
     */
    public class DepthFirstOrder {
        private boolean[] marked;
        private Queue<Integer> pre;         // 前序排列
        private Queue<Integer> post;        // 后序排列
        private Stack<Integer> reversePost; // 逆后序排列
    
        public DepthFirstOrder(Digraph G) {
            pre = new Queue<Integer>();
            post = new Queue<Integer>();
            reversePost = new Stack<Integer>();
            marked = new boolean[G.V()];
            for (int v = 0; v < G.V(); v++)
                if (!marked[v]) 
                	dfs(G, v);
        }
    
        private void dfs(Digraph G, int v) {
            pre.enqueue(v);   //
            marked[v] = true;
            for (int w : G.adj(v))
                if (!marked[w]) 
                	dfs(G, w);
            post.enqueue(v);     //
            reversePost.push(v);   //
        }
    
        public Iterable<Integer> pre() { return pre; }
        public Iterable<Integer> post() { return post; }
        public Iterable<Integer> reversePost() { return reversePost; }
    }
    

    在这里插入图片描述


    拓扑排序

    优先级限制下的调度问题等价于计算有向无环图中所有顶点的拓扑排序

    当且仅当一幅有向图是无环图时,它才能进行拓扑排序。

    在这里插入图片描述

    下述算法使用了DepthFirstOrder类DirectedCycle类来返回一幅有向无环图的拓扑顺序。Topological类 的实现使用了DFS来对有向无环图进行拓扑排序。
    在这里插入图片描述

    /*
     * 拓扑排序
     */
    public class Topological {
        private Iterable<Integer> order;   // 顶点的拓扑顺序
    
        public Topological(Digraph G) {
            DirectedCycle cyclefinder = new DirectedCycle(G);
            if (!cyclefinder.hasCycle()) {
                DepthFirstOrder dfs = new DepthFirstOrder(G);
                order = dfs.reversePost();   //排序方式 逆后序
            }
        }
    
        public Iterable<Integer> order() { return order; }
        public boolean isDAG() { return order == null; }
    }
    

    使用深度优先搜索对有向无环图进行拓扑排序所需的时间和 V + E V+E V+E 成正比。
    第一遍DFS保证了不存在有向环,第二遍DFS产生了顶点的逆后序排列。两次搜索都访问了所有的顶点和边,因此它所需的时间和 V + E V+E V+E 成正比。


    在实际应用中,拓扑排序有向环的检测总会一起出现,因为有向环的检测是排序的前提。
    因此,解决任务调度类应用通常需要以下3步:
    – 指明任务和优先级条件
    – 不断检测并去除有向图中的所有环,以确保存在可行方案
    – 使用拓扑排序解决调度问题



    展开全文
  • 有向无环图VS树

    万次阅读 2017-09-20 22:52:52
    有向无环图VS树前言: Big-man在看着终极算法的时候,突然一个和要好的朋友抛出了一数据结构有关的问题: 有向无环图 VS 树。Big-man想着他们之间有什么差别了,虽然这样想着。但是Big-man还是想着需要去分析一下的。...
  • 简单实现有向无环图思路

    千次阅读 2019-05-13 15:59:47
    给一部分带有from和to的节点组织成一个有向无环图,给from与to路径找到他们之前的连线轨迹。实现思路:遍历所有的节点,该节点如果不包含from或者to的内容且周围只有一个节点这样的节点从我们总的节点中删除,最终...
  • 一、有向无环图描述表达式 (一)有向无环图(DAG) 有向无环图:若⼀个有向图中不存在环,则称为有向无环图,简称DAG图(Directed Acyclic Graph) (二)DAG描述表达式 29.【2019统考真题】用有向无环图描述表达式...
  • 本节主要讲述有向无环图的相关拓扑排序方法和实现算法。
  • 双数组Trie树高效构建有向无环图

    千次阅读 2018-07-19 08:33:13
    图 图是很常见的一种结构了,不管是数据结构算法...有向无环图,即 Directed Acyclic Graph,属于有向图,图结构中不存在环,可用来表示事件之间依赖关系。 Trie树 Trie 是一种搜索树,它的 key 都为字符串,...
  • 有向无环图表示算术表达式

    千次阅读 2019-12-09 09:38:30
    用有向无环图描述表达式(A+B)*((A+B)/A),至少需要顶点的数目为几个? 分析: 先画出算术表达式 (A+B)*((A+B)/A)的二叉树表示 (A+B)*((A+B)/A) 有重复的子表达式 (A+B),二叉树中可实现对子表达式的共享 ...
  • 有向无环图操作源码,节点可以使任意可插入OLE项
  • 有向无环图 邻接表实现 package main import ( "fmt" "github.com/eapache/queue" ) // 邻接表 type Vertex struct { Key string Parents []*Vertex Children []*Vertex Value interface{} } type DAG ...
  • 有向无环图描述表达式

    千次阅读 多人点赞 2020-07-18 20:22:36
    什么是有向无环图2. 有向无环图描述表达式3. 有向无环图描述表达式的生成步骤 1. 什么是有向无环图 2. 有向无环图描述表达式 3. 有向无环图描述表达式的生成步骤 这样,比较混乱,如果式子一长,不容易看出来。 ...
  • 文章目录有向无环图拓扑排序AOV-网AOE-网关键路径的概念事件的最早/晚开始时间事件和活动的区分活动的最早/晚开始时间 有向无环图 拓扑排序 AOV-网 由于有向无环图可以用一种自然的方式对优先关系或依赖关系进行...
  • E + C:E:D) class(graph) plot(graph) 或者运行以下代码绘制有向无环图(dag) library("gRbase") graph (~A:B:E + C:E:D) dag (~A + B:A + C:B + D:B + E:C:D) plot(dag) 注意观察在dag中 a:b 中第一个节点a是被...
  • 有向无环图(邻接矩阵和邻接表)

    千次阅读 2020-04-08 01:23:39
    一、的定义 是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:  G=(V,E) 其中:G表示一个,V是G中顶点的集合,E是G中顶点之间边的集合。 注: 在线性表中,元素个数可以为零,称为空表...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 169,816
精华内容 67,926
关键字:

无环图