• 最小生成树


      图的生成树是它的一棵含有所有顶点的无环连通子图。一幅加权无向图的最小生成树是它的一棵权值(树中所有边的权值之和)最小的生成树。

      原理:

    • 用一条边连接树中任意两个顶点都会产生一个新的环;
    • 从树中删去一条边都会得到两棵独立的树;

     

    切分定理

      图的一种切分是将图的所有顶点分为两个非空的且不重复的两个集合。横切边是一条连接两个属于不同集合顶点的边。在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树。

      加权无向图边的定义

    public class Edge implements Comparable<Edge> { 
    
        private final int v;
        private final int w;
        private final double weight;
    
        public Edge(int v, int w, double weight) {
            if (v < 0) throw new IndexOutOfBoundsException("Vertex name must be a nonnegative integer");
            if (w < 0) throw new IndexOutOfBoundsException("Vertex name must be a nonnegative integer");
            if (Double.isNaN(weight)) throw new IllegalArgumentException("Weight is NaN");
            this.v = v;
            this.w = w;
            this.weight = weight;
        }
    
        public double weight() {
            return weight;
        }
    
        public int either() {
            return v;
        }
    
        public int other(int vertex) {
            if(vertex == v) return w;
            else if (vertex == w) return v;
            else throw new IllegalArgumentException("Illegal endpoint");
        }
    
        @Override
        public int compareTo(Edge that) {
            return Double.compare(this.weight, that.weight);
        }
    }
    View Code

       加权无向图

    public class EdgeWeightedGraph {
        private static final String NEWLINE = System.getProperty("line.separator");
    
        private final int V;
        private int E;
        private Bag<Edge>[] adj;
        
        public EdgeWeightedGraph(int V) {
            if (V < 0) throw new IllegalArgumentException("Number of vertices must be nonnegative");
            this.V = V;
            this.E = 0;
            adj = (Bag<Edge>[]) new Bag[V];
            for (int v = 0; v < V; v++) {
                adj[v] = new Bag<Edge>();
            }
        }
    
        public int V() {
            return V;
        }
    
        public int E() {
            return E;
        }
    
        public void addEdge(Edge e) {
            int v = e.either();
            int w = e.other(v);
            adj[v].add(e);
            adj[w].add(e);
            E++;
        }
    
        public Iterable<Edge> adj(int v) {
            validateVertex(v);
            return adj[v];
        }
    
        public int degree(int v) {
            validateVertex(v);
            return adj[v].size();
        }
    }
    View Code

    Prim(普利姆)算法

      一开始这棵树只有一个顶点,然后将它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入树中。Prim算法的延时计算一幅含有V个顶点和E条边的连通加权无向图的最小生成树所需的时间与ElongE成正比。算法简单描述如下:

    1. 输入:一个加权连通图,其中顶点集合为V,边集合为E;
    2. 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
    3. 重复下列操作,直到Vnew = V;

        a)在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);

        b)将v加入集合Vnew中,将<u, v>边加入集合Enew中;

      4输出:使用集合Vnew和Enew来描述所得到的最小生成树。

      Prim算法的延时实现:

    public class LazyPrimMST {
        private static final double FLOATING_POINT_EPSILON = 1E-12;
    
        private double weight;       // total weight of MST
        private Queue<Edge> mst;     // edges in the MST
        private boolean[] marked;    // marked[v] = true if v on tree
        private MinPQ<Edge> pq;      // edges with one endpoint in tree
    
        public LazyPrimMST(EdgeWeightedGraph G) {
            mst = new Queue<Edge>();
            pq = new MinPQ<Edge>();
            marked = new boolean[G.V()];
            for (int v = 0; v < G.V(); v++)     // run Prim from all vertices to
                if (!marked[v]) 
                    prim(G, v);     // get a minimum spanning forest
    
        }
    
        private void prim(EdgeWeightedGraph G, int s) {
            scan(G, s);
            while (!pq.isEmpty()) {    // better to stop when mst has V-1 edges
                Edge e = pq.delMin();  // smallest edge on pq
                int v = e.either(), w = e.other(v); // two endpoints
                assert marked[v] || marked[w];
                if (marked[v] && marked[w]) 
                    continue; // lazy, both v and w already scanned
                mst.enqueue(e);  // add e to MST
                weight += e.weight();
                if (!marked[v]) scan(G, v); // v becomes part of tree
                if (!marked[w]) scan(G, w); // w becomes part of tree
            }
        }
    
        // add all edges e incident to v onto pq if the other endpoint has not yet been scanned
        private void scan(EdgeWeightedGraph G, int v) {
            assert !marked[v];
            marked[v] = true;
            for (Edge e : G.adj(v))
                if (!marked[e.other(v)]) pq.insert(e);
        }
            
        public Iterable<Edge> edges() {
            return mst;
        }
    
        public double weight() {
            return weight;
        }
    }
    
    
                
    View Code

      Prim算法的即时实现:

    public class PrimMST {
        private static final double FLOATING_POINT_EPSILON = 1E-12;
        private Edge[] edgeTo; // edgeTo[v] = shortest edge from tree vertex to non-tree vertex
        private double[] distTo; // distTo[v] = weight of shortest such edge
        private boolean[] marked; // marked[v] = true if v on tree, false otherwise
        private IndexMinPQ<Double> pq;
    
      
        public PrimMST(EdgeWeightedGraph G) {
            edgeTo = new Edge[G.V()];
            distTo = new double[G.V()];
            marked = new boolean[G.V()];
            pq = new IndexMinPQ<Double>(G.V());
            for (int v = 0; v < G.V(); v++)
                distTo[v] = Double.POSITIVE_INFINITY;
    
            for (int v = 0; v < G.V(); v++)      // run from each vertex to find
                if (!marked[v]) prim(G, v);      // minimum spanning forest
        }
    
        // run Prim's algorithm in graph G, starting from vertex s
        private void prim(EdgeWeightedGraph G, int s) {
            distTo[s] = 0.0;
            pq.insert(s, distTo[s]);
            while (!pq.isEmpty()) {
                int v = pq.delMin();
                scan(G, v);
            }
        }
    
        // scan vertex v
        private void scan(EdgeWeightedGraph G, int v) {
            marked[v] = true;
            for (Edge e : G.adj(v)) {
                int w = e.other(v);
                if (marked[w]) 
                    continue; // v-w is obsolete edge
                if (e.weight() < distTo[w]) {
                    distTo[w] = e.weight();
                    edgeTo[w] = e;
                    if (pq.contains(w)) 
                        pq.decreaseKey(w, distTo[w]);
                    else              
                        pq.insert(w, distTo[w]);
                }
            }
        }
    
        public Iterable<Edge> edges() {
            Queue<Edge> mst = new Queue<Edge>();
            for (int v = 0; v < edgeTo.length; v++) {
                Edge e = edgeTo[v];
                if (e != null) {
                    mst.enqueue(e);
                }
            }
            return mst;
        }
    
        public double weight() {
            double weight = 0.0;
            for (Edge e : edges())
                weight += e.weight();
            return weight;
        }
    }
    View Code

    Kruskal(克鲁斯卡尔)算法

      算法描述:克鲁斯卡尔算法需要对图的边进行访问,所以克鲁斯卡尔算法的时间复杂度只和边又关系,可以证明其时间复杂度为O(eloge)。算法过程如下:

    1. 将图各边按照权值进行排序
    2. 将图遍历一次,找出权值最小的边,(条件:此次找出的边不能和已加入最小生成树集合的边构成环),若符合条件,则加入最小生成树的集合中。不符合条件则继续遍历图,寻找下一个最小权值的边。
    3. 递归重复步骤1,直到找出n-1条边为止(设图有n个结点,则最小生成树的边数应为n-1条),算法结束。得到的就是此图的最小生成树。
    public class KruskalMST {
        private static final double FLOATING_POINT_EPSILON = 1E-12;
    
        private double weight; // weight of MST
        private Queue<Edge> mst = new Queue<Edge>();  // edges in MST
    
       
        public KruskalMST(EdgeWeightedGraph G) {
            // more efficient to build heap by passing array of edges
            MinPQ<Edge> pq = new MinPQ<Edge>();
            for (Edge e : G.edges()) {
                pq.insert(e);
            }
    
        
            UF uf = new UF(G.V());
            while (!pq.isEmpty() && mst.size() < G.V() - 1) {
                Edge e = pq.delMin();
                int v = e.either();
                int w = e.other(v);
                if (!uf.connected(v, w)) { // v-w does not create a cycle
                    uf.union(v, w);  // merge v and w components
                    mst.enqueue(e);  // add edge e to mst
                    weight += e.weight();
                }
            }
        }
    
        public Iterable<Edge> edges() {
            return mst;
        }
    
        public double weight() {
            return weight;
        }    
    }
    View Code
  • 相关阅读:
    组件通信一 父子之间的通信方式
    我常用的tmux命令
    从标准输入流中读取并执行shell指定函数
    弱网测试浏览器及微信开发者工具
    jmeter性能测试实例解析
    jmeter多个线程组的并行和串行
    解析mybatis plus分页不起作用(失败)的问题
    atable 中 数据已更新,但是表格里面不显示改变
    typescript map()和filter()
    C#编程的最佳工具
  • 原文地址:https://www.cnblogs.com/wxgblogs/p/5709005.html
Copyright © 2020-2023  润新知