有向无环图的拓扑排序及最小生成树算法(Prim+Kruskal)

数据结构和算法 专栏收录该内容
20 篇文章 0 订阅

一、拓扑排序

1、拓扑排序目标

       对于有向无环图,拓扑排序的目标其实就是找出依赖关系的顺序。

Alt
       上面那幅图的拓扑排序就是A B C D E F 或 A B D C E F



2、算法思路

       先找到入度为0的顶点依次入队,每次从队头出队一个顶点(可以看成是从这幅图中删除),由此更新该顶点出度边终点的入度信息,一旦有新的入度为0的顶点就立刻将这个点入队,直到队列为空。由此出队的顺序其实就是拓扑排序的结果。



3、注意点

       怎么维护入度信息?用一个哈希表记录除了起始入度就为0的所有结点的入度信息,每次从队列中取出一个顶点就更新这个哈希表即可。

       怎么保存结果集?因为结果是严格要求顺序的,可用动态数组或者链表保存。


4、实现

    /**
     * 有向无环图的拓扑排序
     */
    @Override
    public List<E> topologicalSort() {
        List<E> result = new ArrayList<>();
        Map<E, Integer> table = new HashMap<>();           //哈希表维护所有结点的入度
        Queue<Vertex<E, V>> queue = new LinkedList<>();
        Set<E> keys = vertices.keySet();
        for (E e : keys) {
            Vertex<E, V> vertex = vertices.get(e);
            int size = vertex.inEdges.size();
            if (size == 0)
                queue.offer(vertex);                    //队列初始化
            else
                table.put(e, vertex.inEdges.size());     //哈希表初始化
        }
        while (!queue.isEmpty()) {
            Vertex<E, V> vertex = queue.poll();
            result.add(vertex.element);
            for (Edge<E, V> edge : vertex.outEdges) {
                Integer size = table.get(edge.to.element);
                if (--size == 0) {
                    queue.offer(edge.to);               //一旦结点的入度为0立刻入队
                    table.remove(edge.to.element);
                } else {
                    table.put(edge.to.element, size);    //更新由出队结点指向的其他结点的入度
                }
            }
        }
        return result;
    }






二、最小生成树算法(无向图)

1、最小生成树算法目标

       生成树:连通图的极小连通子图。即 n-1 条边连接 n 个顶点。

       最小生成数的目标就是找到所有生成树中总权值最小的一棵树



2、Prim算法

① 算法思路

       选取一个点作为起点,将起点加入集合,从集合中所有点的出度边中选出权值最小的一条边加入到结果集中,并将这条边的终点也加到集合中,不断执行上述操作,直到集合中的顶点数量达到图中所有的顶点数,则算法结束。

       这个算法中自始自终只有一个集合,所以可用set模拟并查集。

Alt

② 注意点

       怎么挑选权值最小的边?可以用优先队列来保存所有边,但是出队最小边的时候需要注意,不能让原来结果集里面的顶点成环!


③ 实现
		private Set<EdgeInfo<E, V>> prim() {
	        Set<EdgeInfo<E, V>> result = new HashSet<>();
	        Set<Vertex<E, V>> addedVertices = new HashSet<>();
	        int desPointSize = vertices.size();
	        PriorityQueue<Edge<E, V>> queue = new PriorityQueue<>(new Comparator<Edge<E, V>>() {
	            @Override
	            public int compare(Edge<E, V> o1, Edge<E, V> o2) {
	                return weightAbout.compareWeight(o1.weight, o2.weight);
	            }
	        });
	
	        Iterator<Vertex<E, V>> it = vertices.values().iterator();
	        Vertex<E, V> vertex = it.next();
	        for (Edge<E, V> edge : vertex.outEdges)
	            queue.offer(edge);              //初始化优先队列
	        addedVertices.add(vertex);
	
	        while (!queue.isEmpty() && addedVertices.size() < desPointSize) {
	            Edge<E, V> minEdge = queue.poll();      //取出权值最小的边
	            Vertex<E, V> toVertex = minEdge.to;
	            if (addedVertices.contains(toVertex))   //避免成环
	                continue;
	            toVertex.outEdges.forEach((Edge<E, V> edge) -> {
	                if (!addedVertices.contains(edge.to))   //避免添加重复边
	                    queue.offer(edge);     //每次都将取出边终点的出度边入队
	            });
	            result.add(minEdge.castToInfo());
	            addedVertices.add(toVertex);
	        }
	        return result;
	    }


3、Kruskal算法

① 算法思路

       按照边的权重顺序(从小到大)将边加入生成树中,直到生成树中含有V – 1 条边为止( V 是顶点数量)。 相当于初始的时候把所有顶点都作为一个单独的集合,选出一条边就将这条边的起点和终点合并为一个集合。当然如果选出边的起点和终点本来就在一个集合中那势必会成环,就只能选下一条边了。

       这个算法涉及到多个集合,只能用并查集来实现了。

Alt

② 实现
		private Set<EdgeInfo<E, V>> kruskal() {
	        Set<EdgeInfo<E, V>> result = new HashSet<>();
	        PriorityQueue<Edge<E, V>> queue = new PriorityQueue<>(new Comparator<Edge<E, V>>() {
	            @Override
	            public int compare(Edge<E, V> o1, Edge<E, V> o2) {
	                return weightAbout.compareWeight(o1.weight, o2.weight);
	            }
	        });
	        int desEdgeSize = vertices.size() - 1;
	        UnionFind<E> uf = new UnionFind<>();
	        edges.forEach((Edge<E, V> edge) -> {    //初始化队列
	            queue.offer(edge);
	        });
	        vertices.values().forEach((Vertex<E, V> vertex) -> {    //初始化集合
	            uf.makeSet(vertex.element);
	        });
	
	        while (!queue.isEmpty() && result.size() < desEdgeSize) {
	            Edge<E, V> minEdge = queue.poll();
	            Vertex<E, V> fromVertex = minEdge.from;
	            Vertex<E, V> toVertex = minEdge.to;
	            if (uf.isSame(fromVertex.element, toVertex.element))     //避免成环
	                continue;
	
	            result.add(minEdge.castToInfo());
	            uf.union(fromVertex.element, toVertex.element);
	        }
	        return result;
	    }
  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值