【排序算法】排序算法-拓扑排序

相关概念

AOV网

  • 一项大的工程常被分为多个小的子工程
    • 子工程之间可能存在一定的先后顺序,即某些子工程必须在其他的一些子工程完成后才能开始
  • 在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,子工程被称为活动(Activity)
    • 以顶点表示活动、有向边表示活动之间的先后关系,这样的图简称为AOV网
  • 标准的AOV网必须是一个有向无环图(Directed Acyclic Graph, 简称 DAG)

在这里插入图片描述

拓扑排序

在这里插入图片描述

  • 前驱活动: 有向边起点的活动称为终点的前驱活动
    • 只有当一个活动的前驱全部都完成后,这个活动才能进行
  • 后继活动: 有向边终点的活动称为起点的后继活动

在这里插入图片描述

  • 什么是拓扑排序?
    • 将AOV网中所有活动排成一个序列,使得每个活动的前驱活动都排在该活动的前面
    • 比如上图的拓扑排序结果是: A、B、C、D、E、F或者是A、B、D、C、E、F(结果并不一定是唯一的)

实现思路

  • 可以使用卡恩算法(kahn于1962年提出)完成拓扑排序
  • 假设L是存放拓扑排序结果的列表
  1. 把所有入度为0的顶点放入L中,然后把这些顶点从图中去掉
  2. 重复操作1,直到找不到入度为0的顶点
  • 如果此时L中的元素个数和顶点总数相同,说明拓扑排序完成
  • 如果此时L中的元素个数少于顶点总数,说明原图中存在环,无法进行拓扑排序

在这里插入图片描述
在这里插入图片描述

实现过程

在这里插入图片描述

  • 首先维护一张入度表和一个list集合,和一个队列
    入度表
    在这里插入图片描述
    队列(初始化将所有的入度为0的顶点放到队列中)
    在这里插入图片描述

  • 将C出队,放到list中,并且遍历所有以C为为起点的边的终点的集合,将这些点的入度-1,如果这些点中存在-1之后入度为0的点,将其加入到队列中。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 将队列中的顶点E出队,放到list中,并且遍历所有以E为为起点的边的终点的集合,将这些点的入度-1, 此轮-1后,点A的入度为0,入队
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • A点出队,放到list集合中,以A点为起点的边的终点集合的入度-1,此轮之后,B,D两点的入度为0,加入到队列中
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • D点出队,加入list集合中
    在这里插入图片描述
    在这里插入图片描述

  • B点出队,加入到list集合中,更新入度表,点F入队在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

  • 点F出队,加入到list集合中
    在这里插入图片描述
    在这里插入图片描述

代码

  • 图的接口类
public interface Graph<V, E> {

    /**
     * 打印边
     */
    void print();

    /**
     * 边的数量
     * @return
     */
    int edgeSize();

    /**
     * 顶点的数量
     * @return
     */
    int vertexSize();

    /**
     * 添加顶点
     * @param v
     */
    void addVertex(V v);

    /**
     * 添加边(无权值)
     * @param from
     * @param to
     */
    void addEdge(V from, V to);

    /**
     * 添加边(有权值)
     * @param from
     * @param to
     * @param weight
     */
    void addEdge(V from, V to, E weight);

    /**
     * 移除顶点
     * @param v
     */
    void removeVertex(V v);

    /**
     * 移除边
     * @param from
     * @param to
     */
    void removeEdge(V from, V to);

    /**
     * 拓扑排序
     * @return
     */
    List<V> topologicalSort();

}

  • 图的实现类
public class ListGraph<V, E> implements Graph<V, E> {

    private Map<V, Vertex<V, E>> vertices = new HashMap<>();
    private Set<Edge<V, E>> edges = new HashSet<>();

    /**
     * 打印
     */
    @Override
    public void print() {
        //打印顶点
        vertices.forEach((v, vertex) -> {
            System.out.println(v);
            System.out.println("out-----------");
            System.out.println(vertex.outEdges);
            System.out.println("in------------");
            System.out.println(vertex.inEdges);
        });
        //打印边
        edges.forEach(edge -> System.out.println(edge));
    }

    @Override
    public int edgeSize() {
        return edges.size();
    }

    @Override
    public int vertexSize() {
        return vertices.size();
    }

    @Override
    public void addVertex(V v) {
        if (vertices.containsKey(v)) {
            return;
        }
        //添加顶点
        vertices.put(v, new Vertex<>(v));

    }

    @Override
    public void addEdge(V from, V to) {
        addEdge(from, to, null);
    }

    @Override
    public void addEdge(V from, V to, E weight) {
        Vertex<V, E> fromVertex = vertices.get(from);
        //如果map中不存在value=from的顶点,则创建并放到map中
        if (fromVertex == null) {
            fromVertex = new Vertex<>(from);
            vertices.put(from, fromVertex);
        }

        //如果map中不存在value=to的顶点,则创建并放到map中
        Vertex<V, E> toVertex = vertices.get(to);
        if (toVertex == null) {
            toVertex = new Vertex<>(to);
            vertices.put(to, toVertex);
        }

        //根据两个顶点创建边
        Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
        //赋权值
        edge.weight = weight;

        //如果edge这条边已存在,就删除edge这条边,在from顶点的outEdges集合中删掉这条边,在to顶点的inEdges集合中删除这条边,直接删除原来的边,重新添加新的边,因为权值可能更新了。
        if (fromVertex.outEdges.remove(edge)) {
            toVertex.inEdges.remove(edge);
        }
        //再次把边添加到两个顶点的两个集合中。
        fromVertex.outEdges.add(edge);
        toVertex.inEdges.add(edge);

        //将边添加到集合edges中
        edges.add(edge);
    }

    @Override
    public void removeVertex(V v) {
        Vertex<V, E> vertex = vertices.remove(v);
        //如果map中不存在value=v的顶点,直接返回
        if (vertex == null) {
            return;
        }

        //删除边集和Edges中的所有和顶点vertex相关的边
        for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {
            Edge<V, E> edge = iterator.next();
            edge.to.inEdges.remove(edge);
            iterator.remove();
            edges.remove(edge);
        }

        for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
            Edge<V, E> edge = iterator.next();
            edge.from.outEdges.remove(edge);
            iterator.remove();
            edges.remove(edge);
        }
    }

    @Override
    public void removeEdge(V from, V to) {
        Vertex<V, E> fromVertex = vertices.get(from);
        //如果map中不存在value=from的顶点,则肯定也没有以from为顶点的边
        if (fromVertex == null) {
            return;
        }

        //如果map中不存在value=to的顶点,则肯定也没有以to为顶点的边
        Vertex<V, E> toVertex = vertices.get(to);
        if (toVertex == null) {
            return;
        }

        //根据两个顶点创建边
        Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
        //删除对应的边
        if (fromVertex.outEdges.remove(edge)) {
            toVertex.inEdges.remove(edge);
            edges.remove(edge);
        }
    }

    /**
     * 顶点
     */
    static class Vertex<V, E> {
        V value;
        Set<Edge<V, E>> inEdges = new HashSet<>();
        Set<Edge<V, E>> outEdges = new HashSet<>();

        public Vertex(V value) {
            this.value = value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Vertex<V, E> vertex = (Vertex<V, E>) o;
            return Objects.equals(value, vertex.value);
        }

        @Override
        public int hashCode() {
            return Objects.hash(value);
        }

        @Override
        public String toString() {
            return value == null ? "null" : value.toString();
        }
    }

    @Override
    public List<V> topologicalSort() {
        //拓扑排序后的结果
        List<V> list =new ArrayList<>();
        Queue<Vertex<V,E>> queue = new LinkedList<>();
        //使用map维护每个顶点的出度
        Map<Vertex<V, E>, Integer> ins = new HashMap<>();
        vertices.forEach((v, vertex) -> {
            //获取以vertex为终点的边的个数,即入度大小
            int in = vertex.inEdges.size();
            //如果入度为0,放到队列中,否则将顶点的入度放到map(顶点 -> 顶点的入度)中
            if (in == 0) {
                queue.offer(vertex);
            } else {
                ins.put(vertex, in);
            }
        });

        //当队列非空
        while (!queue.isEmpty()) {
            //队列元素出队
            Vertex<V, E> vertex = queue.poll();
            //放入返回结果中
            list.add(vertex.value);
            //遍历所有以该顶点为起点的边的集合,将边的重点的度
            for (Edge<V, E> edge : vertex.outEdges) {
                //终点的入度-1
                int toIn = ins.get(edge.to) - 1;
                //入度为0加入队列,否则更新map中维护的顶点的入度
                if (toIn == 0) {
                    queue.offer(edge.to);
                } else {
                    ins.put(edge.to, toIn);
                }
            }
        }

        return list;
    }

    /**
     * 边
     */
    static class Edge<V, E> {
        Vertex<V, E> from;
        Vertex<V, E> to;
        E weight;

        public Edge(Vertex<V, E> from, Vertex<V, E> to) {
            this.from = from;
            this.to = to;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Edge<V, E> edge = (Edge<V, E>) o;
            return Objects.equals(from, edge.from) && Objects.equals(to, edge.to);
        }

        @Override
        public int hashCode() {
            return Objects.hash(from, to);
        }

        @Override
        public String toString() {
            return "Edge{" +
                    "from=" + from +
                    ", to=" + to +
                    ", weight=" + weight +
                    '}';
        }
    }

}

测试

测试类

public class TopologicalSort {
    public static final Object[][] TOPO = {
            {0, 2},
            {1, 0},
            {2, 5}, {2, 6},
            {3, 1}, {3, 5}, {3, 7},
            {5, 7},
            {6, 4},
            {7, 6}
    };


    /**
     * 生成有向图
     */
    private static Graph<Object, Double> directedGraph(Object[][] data) {
        Graph<Object, Double> graph = new ListGraph<>();
        for (Object[] edge : data) {
            if (edge.length == 1) {
                graph.addVertex(edge[0]);
            } else if (edge.length == 2) {
                graph.addEdge(edge[0], edge[1]);
            }
        }
        return graph;
    }

    public static void main(String[] args) {
        //构建有向图
        Graph<Object, Double> graph = directedGraph(TOPO);
        //对图进行拓扑排序
        List<Object> list = graph.topologicalSort();
        System.out.println(list);
    }

}

测试样例

  • 构造的图如下图所示
    在这里插入图片描述
  • 排序的输出结果
    在这里插入图片描述
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值