拓扑排序与有向无环图

基本概念:


一个无环的有向图称为 有向无环图(Directed Acycline Graph,DAG)。
有向无环图是描述一个工程、计划、生产、系统等流程的有效工具。一个大工程可分为 若干个子工程(活动),活动之间通常有一定的约束,例如先做什么活动、后做什么活动。
用顶点表示活动,用弧表示活动之间的优先关系的有向图,称为顶点表示活动的网 (Activity On Vertex Network),简称 AOV 网。
拓扑排序 是指将 AOV 网中的顶点排成一个线性序列,该序列必须满足:若从顶点 i 到顶点 j 有一条路径,则该序列中 顶点 i 一定在顶点 j 之前。

拓扑排序的基本思想如下:

1)选择一个无前驱的顶点并输出。

2)从图中删除该顶点和该顶点的所有发出边。

3)重复第 1 步和第 2 步,直到不存在无前驱的顶点。

4)如果输出的顶点数小于 AOV 网中的顶点数,则说明网中有环,否则输出的序列即拓扑序列。

需要注意的是,拓扑排序并不是唯一的,当存在多个无前驱的顶点时,可以选择其中任意一个输出。

上述的描述过程中有删除顶点和边的操作,实际上,完全没必要真的删除顶点和边。可 以将没有前驱的顶点(入度为 0)暂存到栈中,输出时出栈即表示删除。边的删除只需要将 其邻接点的入度减 1 即可。

拓扑排序的算法步骤如下:

1)求出各顶点的入度,存入数组 indegree[]中,并将入度为 0 的顶点入栈 S。

2)如果栈不空,则重复执行以下操作:

        • 栈顶元素 i 出栈,并保存到拓扑序列数组 topo[]中;

        • 顶点 i 的所有邻接点入度减 1,如果减 1 后入度为 0,立即入栈 S。

3)如果输出的顶点数小于 AOV 网中的顶点数,则说明网中有环,否则输出拓扑序列。

算法复杂度分析

(1)时间复杂度 求有向图中各顶点的入度需要遍历邻接表,算法的时间复杂度为 O(e)。度数为 0 的顶点 入栈的时间复杂度为 O(n),若有向图无环,每个顶点出栈后其邻接点入度减 1,时间复杂度为 O(e)。总的时间复杂度为 O(n+e)。

(2)空间复杂度 算法所需要的辅助空间包含入度数组 indegree[]、拓扑序列数组 topo[]、栈 S,则算法的 空间复杂度是 O(n)。

参考:《趣学数据结构》作者:陈小玉

图有很多种存储方法,其中,邻接矩阵和邻接表是两种简单且常用的方法。

在进行拓扑排序时,因为删除顶点 i 时,要将顶点 i 的所有邻接点入度减 1,访问一个顶点的所有邻接点, 使用邻接表存储需要时间复杂度为该顶点的度,邻接矩阵需要 O(n),因此这里采用邻接表存储。

有向图邻接表求出度容易,而逆邻接表求入度容易。

package com.ymzh.demo4IoC;

import java.util.*;

public class Util4DAG {
    private static class Reference {
        private final Integer bean;
        private final Integer ref;
        
        public Reference (Integer bean, Integer ref) {
            this.bean = bean;
            this.ref = ref;
        }

        public Integer getBean() {
            return bean;
        }

        public Integer getRef() {
            return ref;
        }
    }
    /**
     * 根据边集合生成图的邻接表,并计算入度
     * @param references    边集合
     * @param graph         图的邻接表
     * @param inDegree      图的入度
     */
    public static void CreateGraphAndInDegree(List<Reference> references, Map<Integer, List<Integer>> graph, Map<Integer, Integer> inDegree){
        // 初始化 邻接表 和 入度
        for (Reference reference : references) {
            if (!graph.containsKey(reference.getBean())) {
                graph.put(reference.getBean(), new ArrayList<>());
                inDegree.put(reference.getBean(), 0);
            }
        }
        // 创建邻接表, 并计算入度
        for (Reference reference : references) {
            if(reference.getRef() != null) {
                graph.get(reference.getRef()).add(reference.getBean());
                inDegree.put(reference.getBean(), inDegree.get(reference.getBean())+1);
            }
        }
    }

    /**
     *
     * @param graph         图的邻接表
     * @param inDegree      图的入度
     * @param topo          图的拓扑排序
     * @return              是否存在拓扑排序
     * 若 G 无回路,则生成 G 的一个拓扑序列 topo[],并返回 true,否则 false
     */
    public static boolean TopologicalSort(Map<Integer, List<Integer>> graph, Map<Integer, Integer> inDegree, List topo) {
        // 辅助栈
        Deque<Integer> stack = new ArrayDeque<>();
        //入度为 0 者进栈
        for(Integer key : graph.keySet()) {
            if(inDegree.get(key) == 0){
                stack.addLast(key);
            }
        }

        while (!stack.isEmpty()) { //栈非空
            Integer cur = stack.removeLast(); //取栈顶顶点 cur
            topo.add(cur); //将 cur 保存在拓扑序列数组 topo 中
            // 遍历 cur 的邻接点
            for (Integer next : graph.get(cur)) {
                inDegree.put(next, inDegree.get(next) - 1); // cur 的每个邻接点的入度减 1
                if (inDegree.get(next) == 0) //若入度减为 0, 则入栈
                    stack.push(next);
            }
            // 迭代器遍历
//            Iterator<Integer> iter = graph.get(cur).iterator();
//            while (iter.hasNext()) {
//                Integer next = iter.next();
//                inDegree.put(next, inDegree.get(next) - 1); // cur 的每个邻接点的入度减 1
//                if (inDegree.get(next) == 0) //若入度减为 0, 则入栈
//                    stack.push(next);
//            }

        }
        // index < inDegree.size() 时, 图存在回路, 不存在拓扑排序
        return topo.size() >= inDegree.size();
    }

    public static void main(String[] args) {
        // 实现创建好 用于保存 入度 和 图 的集合。
        Map<Integer, Integer> inDegree = new HashMap<>();
        Map<Integer, List<Integer>> graph = new HashMap<>();
        // 边集合, 其中 ref 为起点, bean 为终点
        ArrayList<Reference> references = new ArrayList<>(){{
            add(new Reference(0, null));
            add(new Reference(1, 0));
            add(new Reference(1, 2));
            add(new Reference(2, 0));
            add(new Reference(3, 0));
            add(new Reference(3, 5));
            add(new Reference(4, 2));
            add(new Reference(4, 3));
            add(new Reference(4, 5));
            add(new Reference(5, null));
        }};
        // 根据边集合, 创建 图, 并计算 入度
        CreateGraphAndInDegree(references, graph, inDegree);

        // 实现创建好 用于 拓扑排序的 集合
        ArrayList topo = new ArrayList();
        TopologicalSort(graph, inDegree, topo);

        System.out.println(topo.toString());
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值