精华内容
下载资源
问答
  • 在这些情况下, d3-hierarchy可能无法满足您的需求,这就是为什么存在d3-dag (有向无环图)的原因。 该模块实现了用于处理DAG的数据结构。 旧版本旨在尽可能地模仿d3-hierarchy的api,新版本则选择使用现代...
  • 有向无环图(DAG)的实现。 该实现是快速且线程安全的。 它可以防止添加循环或重复,从而始终保持有效的DAG。 该实现缓存后代和祖先,以加快后续调用的速度。 快速开始 跑步: package main import ( "fmt" ...
  • 达格 有向无环图的Golang实现 总览 这个包...
  • 判断给定的图是不是是有向无环图,方法是应用拓扑排序,代码如下
  • 比较、聚合和聚类有向无环图 (DAG)。 纸张代码: Eric Malmi、Nikolaj Tatti 和 Aristides Gionis,“超越排名:比较有向无环图”。 在数据挖掘和知识发现中,2015 年。 主要文件 dag_dist.m DAG 的距离度量。 ...
  • Layout algorithms for visualizing directed acylic graphs
  • NULL 博文链接:https://128kj.iteye.com/blog/1675685
  • 为研究基于有向无环图的支持向量机分类算法以及在故障诊断问题中的应用,考虑到有向无环图的结构运算相当于一个表操作,且分类结果依赖于有向无环图中节点的排列顺序,提出一种分类算法,该算法引入基于类分布的类间...
  • 采用深度优先算法(DFS)遍历有向无环图寻找最优路径,经过优化的深度优先算法,在遍历有向无环图的时候保存路径,并计算路径权值,最总返回最优路径及最路径的权值
  • 71图的定义和术语 72图的存储结构 73图的遍历 74图的连通性问题 7.5有向无环图及其应用 7.6最短路径 7.5有向无环图及其应用 75.1有向无环图 有向无环图( directed acycline graph:无环的向图,简称DAG 图DAG图是一...
  • 本节主要讲述有向无环图的相关拓扑排序方法和实现算法。
  • 矢量坎 一个有向无环图看板 大小和方向。
  • 有向无环图中具有长度约束的最大不相交路径问题进行研究,该问题是求解图中两点间路径长度为 k的最大不相交路径。为了对该问题进行求解,提出了贪婪搜索算法(GP,greedy path),该算法先将一个有向无环图转化为一棵...
  • 带你了解有向无环图和拓扑排序

    千次阅读 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 有向环图若个有向图中不存在环则称为有向环图简称DAG图 Directed Acyclic Graph V1 V2 V1 V2 V0 V0 V4 V3 V4 V3 王道考研/ DAG描述表达式 * * + * * + e + * + ...
  • 主要为大家详细介绍了python使用xlsxwriter实现有向无环图到Excel的转换,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 图论算法——环和有向无环图

    万次阅读 多人点赞 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 + " ");
                }
            }
        }
    }
    
    
    展开全文
  • 有向无环图的判断 深度优先遍历 广度优先遍历 图的着色问题 BFS DFS
  • 环和有向无环图

    千次阅读 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步:
    – 指明任务和优先级条件
    – 不断检测并去除有向图中的所有环,以确保存在可行方案
    – 使用拓扑排序解决调度问题



    展开全文
  • 今天小编就为大家分享一篇python判断无向图环是否存在的示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 有向无环图

    千次阅读 2016-09-03 23:05:48
    有向无环图

    /*

    Problem B: 有向无环图
    Time Limit: 5 Sec Memory Limit: 128 MB
    Submit: 20 Solved: 11
    [Submit][Status][Web Board]
    Description

    Bobo 有一个 n 个点,m 条边的有向无环图(即对于任意点 v,不存在从点 v 开始、点 v 结束的路径)。

    为了方便,点用 1,2,…,n 编号。 设 count(x,y) 表示点 x 到点 y 不同的路径数量(规定 count(x,x)=0),Bobo 想知道

    对所有i,j,求f(e(i,j))*(ai,bj)

    除以 (109+7) 的余数。其中f(e(i,j))表示i—>所有边的数目.

    其中,ai,bj 是给定的数列。

    Input

    输入包含不超过 15 组数据。

    每组数据的第一行包含两个整数 n,m (1≤n,m≤105).

    接下来 n 行的第 i 行包含两个整数 ai,bi (0≤ai,bi≤109).

    最后 m 行的第 i 行包含两个整数 ui,vi,代表一条从点 ui 到 vi 的边 (1≤ui,vi≤n)。

    Output

    对于每组数据,输出一个整数表示要求的值。

    Sample Input
    3 3
    1 1
    1 1
    1 1
    1 2
    1 3
    2 3
    2 2
    1 0
    0 2
    1 2
    1 2
    2 1
    500000000 0
    0 500000000
    1 2

    Sample Output
    4
    4
    250000014
    */

    /*
    题意:给一个有向无环图,顶点编号1~n,m条边(注意,多重图),同时,给ai,bi,1<=i<=n; 求:对所有1<=i,j<=n, count(i,j)*a[i]*b[j] 的和对1e9+7取模.
    思路:在回来的车上教练讨论过,听了一点,不太清楚,后来自己去思考.感觉很妙!然后等重现实现Orz…
    主要是妙在求解顺序上,是个拓扑排序.dfs一般超时,bfs分层O(n)吧; 每次先计算入度为零的顶点v与其邻点u的值,然后
    把这个顶点v的a累加到下个顶点u上(v->u),这样当计算到u到它的邻点w的时候,a[u]事实上是已经包括a[v]了,所以算u->w
    的时候顺便把v->w也算了; 不得不说,真的很妙的解法啊!!!
    */

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <vector>
    #include <queue>
    
    using namespace std;
    
    typedef long long LL ;
    const LL mod=1e9+7;
    const int maxn=1e5+100;
    int n,m;
    int a[maxn],b[maxn];
    vector<int> G[maxn];
    int vis[maxn];//入度
    void init()
    {
        for (int i=1;i<=n;i++){
            vis[i]=0;
            G[i].clear();
        }
    }
    int main()
    {
        //freopen("D:in.txt","r",stdin);
        while (scanf("%d%d",&n,&m)==2)
        {
            init();
            for (int i=1;i<=n;i++){
                scanf("%d%d",&a[i],&b[i]);
            }
            for (int i=1;i<=m;i++){
                int a,b;
                scanf("%d%d",&a,&b);
                G[a].push_back(b);
                vis[b]++;
            }
            queue<LL> que;
            for (int i=1;i<=n;i++){
                if (!vis[i]) {que.push(i);}//所有入度为零的入队列
            }
            LL res=0;
            while (!que.empty())
            {
                int v=que.front();que.pop();
                for (int i=0;i<(int)G[v].size();i++){
                    int &u=G[v][i];
                    res=(res+(a[v]%mod) * (b[u]%mod)%mod)%mod;
                    a[u]=(a[u]+a[v])%mod;//把部分现在的结果加到下个顶点来算
                    vis[u]--;
                    if (!vis[u]){//入度为零又可以入队了
                        que.push(u);
                    }
                }
            }
            cout<<res%mod<<endl;
        }
        return 0;
    }
    展开全文
  • 数据调度系统中有向无环图的无环检测 名词解释 DAG,全称:Directed Acyclic Graph,中文:有向无环图 入度:向图中某点作为图中边的终点的次数之和 出度: 对于向图来说,顶点的出边条数即为该顶点的出度 调度...
  • 有向无环图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的内容且周围只有一个节点这样的节点从我们总的节点中删除,最终...
  • 有向无环图操作源码,节点可以使任意可插入OLE项
  • 有向无环图表示算术表达式

    千次阅读 2019-12-09 09:38:30
    有向无环图描述表达式(A+B)*((A+B)/A),至少需要顶点的数目为几个? 分析: 先画出算术表达式 (A+B)*((A+B)/A)的二叉树表示 (A+B)*((A+B)/A) 重复的子表达式 (A+B),二叉树中可实现对子表达式的共享 ...
  • 双数组Trie树高效构建有向无环图

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

    热门讨论 2009-08-14 10:05:36
    基于有向无环图的SVM多类分类,MATLAB编写实现的
  • 有向无环图描述表达式

    千次阅读 多人点赞 2020-07-18 20:22:36
    什么是有向无环图2. 有向无环图描述表达式3. 有向无环图描述表达式的生成步骤 1. 什么是有向无环图 2. 有向无环图描述表达式 3. 有向无环图描述表达式的生成步骤 这样,比较混乱,如果式子一长,不容易看出来。 ...
  • 有向无环图 邻接表实现 package main import ( "fmt" "github.com/eapache/queue" ) // 邻接表 type Vertex struct { Key string Parents []*Vertex Children []*Vertex Value interface{} } type DAG ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 116,663
精华内容 46,665
关键字:

有向无环图