zoukankan      html  css  js  c++  java
  • 有向图和拓扑排序

    有向图

    接着上一篇:无向图

    在实际生活中,很多应用相关的图都是有方向性的,最直观的就是网络,可以从A页面通过链接跳转到B页面,那么a和b连接的方向是a->b,但不能说是b->a,此时我们就需要使用有向图来解决这一类问题,它和我们之前学习的无向图,最大的区别就在于连接是具有方向的,在代码的处理上也会有很大的不同。

    image-20210827160611129

    有向图的定义及相关术语

    定义: 有向图是一副具有方向性的图,是由一组顶点和一组有方向的边组成的,每条方向的边都连着一对有序的顶点。

    出度: 由某个顶点指出的边的个数称为该顶点的出度。

    入度: 指向某个顶点的边的个数称为该顶点的入度。

    有向路径: 由一系列顶点组成,对于其中的每个顶点都存在一条有向边,从它指向序列中的下一个顶点。

    有向环: 一条至少含有一条边,且起点和终点相同的有向路径。

    image-20210827160815553

    一副有向图中两个顶点v和w可能存在以下四种关系:

    1. 没有边相连;
    2. 存在从v到w的边v—>w;
    3. 存在从w到v的边w—>v;
    4. 既存在w到v的边,也存在v到w的边,即双向连接;

    代码实现

    api设计:

    类名 Digraph
    构造方法 Digraph(int V):创建一个包含V个顶点但不包含边的有向图
    构造方法 1.public int V():获取图中顶点的数量
    2.public int E():获取图中边的数量
    3.public void addEdge(int v,int w):向有向图中添加一条边 v->w
    4.public Queue adj(int v):获取由v指出的边所连接的所有顶点
    5.private Digraph reverse():该图的反向图
    成员变量 1.private final int V: 记录顶点数量
    2.private int E: 记录边数量
    3.private Queue[] adj: 邻接表

    代码如下:

    /**
     * 有向图
     * @author wen.jie
     * @date 2021/8/27 15:56
     */
    public class Digraph extends Graph{
        
        public Digraph(int v) {
            super(v);
        }
    
        //向图中添加一条边 v->w
        @Override
        public void addEdge(int v, int w) {
            adj[v].enqueue(w);
            E++;
        }
    
        //该图的反向图
        private Digraph reverse() {
            Digraph digraph = new Digraph(V);
            for (int v = 0; v < V; v++) {
                for (Integer w : adj[v]) {
                    digraph.addEdge(w, v);
                }
            }
            return digraph;
        }
    
    }
    

    拓扑排序

    在现实生活中,我们经常会同一时间接到很多任务去完成,但是这些任务的完成是有先后次序的。以我们学习java学科为例,我们需要学习很多知识,但是这些知识在学习的过程中是需要按照先后次序来完成的。从java基础,到jsp/servlet,到ssm,到springboot等是个循序渐进且有依赖的过程。在学习jsp前要首先掌握java基础和html基础,学习ssm框架前要掌握jsp/servlet之类才行。

    image-20210827163737342

    为了简化问题,我们使用整数为顶点编号的标准模型来表示这个案例:

    image-20210827163753581

    此时如果某个同学要学习这些课程,就需要指定出一个学习的方案,我们只需要对图中的顶点进行排序,让它转换为一个线性序列,就可以解决问题,这时就需要用到一种叫拓扑排序的算法。

    拓扑排序:

    给定一副有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素,此时就可以明确的表示出每个顶点的优先级。下列是一副拓扑排序后的示意图:

    image-20210827164118591

    检测有向图中的环

    如果学习x课程前必须先学习y课程,学习y课程前必须先学习z课程,学习z课程前必须先学习x课程,那么一定是有问题了,我们就没有办法学习了,因为这三个条件没有办法同时满足。其实这三门课程x、y、z的条件组成了一个环:

    image-20210827164334028

    因此,如果我们要使用拓扑排序解决优先级问题,首先得保证图中没有环的存在。

    检测有向环的API设计:

    类名 DirectedCycle
    构造方法 DirectedCycle(Digraph G):创建一个检测环对象,检测图G中是否有环
    构造方法 1.private void dfs(Digraph G,int v):基于深度优先搜索,检测图G中是否有环
    2.public boolean hasCycle():判断图中是否有环
    成员变量 1.private boolean[] marked: 索引代表顶点,值表示当前顶点是否已经被搜索
    2.private boolean hasCycle: 记录图中是否有环
    3.private boolean[] onStack: 索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在 搜索的有向路径上

    在API中添加了onStack[] 布尔数组,索引为图的顶点,当我们深度搜索时:

    1. 在如果当前顶点正在搜索,则把对应的onStack数组中的值改为true,标识进栈;
    2. 如果当前顶点搜索完毕,则把对应的onStack数组中的值改为false,标识出栈;
    3. 如果即将要搜索某个顶点,但该顶点已经在栈中,则图中有环;

    image-20210827165051145

    image-20210827165102800

    image-20210827165157607

    image-20210827165238032

    基于深度优先的顶点排序

    api设计:

    类名 DepthFirstOrder
    构造方法 DepthFirstOrder(Digraph G):创建一个顶点排序对象,生成顶点线性序列;
    构造方法 1.private void dfs(Digraph G,int v):基于深度优先搜索,生成顶点线性序列
    2.public Stack reversePost():获取顶点线性序列
    成员变量 1.private boolean[] marked: 索引代表顶点,值表示当前顶点是否已经被搜索
    2.private Stack reversePost: 使用栈,存储顶点序列

    顶点排序实现:

    在API的设计中,我们添加了一个栈reversePost用来存储顶点,当我们深度搜索图时,每搜索完毕一个顶点,把该顶点放入到reversePost中,这样就可以实现顶点排序。

    image-20210827171236640 image-20210827171443994 image-20210827171533838 image-20210827171602017

    代码实现:

    /**
     * @author wen.jie
     * @date 2021/8/27 17:10
     */
    public class DepthFirstOrder {
    
        private boolean[] marked;
    
        private Stack<Integer> reversePost;
    
        public DepthFirstOrder(Digraph G){
    
            //校验是否有环
            if(new DirectedCycle(G).hasCycle())
                throw new RuntimeException("Digraph hasCycle!");
    
            this.marked = new boolean[G.V()];
            this.reversePost = new Stack<>();
    
            //找到图中每一个顶点,让每一个顶点作为入口,调用dfs搜索
            for (int v = 0; v < G.V(); v++) {
                if (!marked[v])
                    dfs(G, v);
            }
        }
    
        private void dfs(Digraph G, int v) {
            marked[v] = true;
            for (Integer w : G.adj(v)) {
                //如果当前顶点w没有搜索,则递归调用dfs进行搜索
                if (!marked[w])
                    dfs(G, w);
            }
            reversePost.push(v);
        }
    
        public Stack<Integer> reversePost() {
            return reversePost;
        }
    
    }
    

    测试:

            Digraph digraph = new Digraph(6);
            digraph.addEdge(0,2);
            digraph.addEdge(2,4);
            digraph.addEdge(4,5);
            digraph.addEdge(0,3);
            digraph.addEdge(3,4);
            digraph.addEdge(1,3);
            DepthFirstOrder order = new DepthFirstOrder(digraph);
            Stack<Integer> stack = order.reversePost();
            for (Integer integer : stack) {
                System.out.println(integer);
            }
    

    image-20210827172920970

    本篇所有代码均已上传至:https://gitee.com/wj204811/algorithm

  • 相关阅读:
    vue 初始化项目模板报错
    092117-6265-01.dmp 蓝屏日志文件
    电信流氓注入JS
    DISM
    node.js
    Adobe ZXPInstaller 报错 Installation failed because of a file operation error.
    Microsoft Edge 针对 Web 开发人员更新日志
    What's new in Safari 11.0
    CSS Filter
    accept-language
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/15194682.html
Copyright © 2011-2022 走看看