zoukankan      html  css  js  c++  java
  • 加权无向图 最小生成树 Prim算法 延迟版和即时版 村里修路该先修哪

     本次要解决的问题是:你们村里那些坑坑洼洼的路,到底哪些路才是主干道?

    小明:肯定是哪里都能到得了,并且去哪里都相对比较近,并且被大家共用程度高的路是啊!

    具体是哪几条路呢?今天就可以给出准确答案

    最小生成树的特点

    1。可以到达图中任何一个顶点

    2. 是一颗树(无环)

    3. 最小生成树的边的权重之和是可以链接图中所有顶点的边的集合中,权值之和最小的(运用了贪婪算法思想)

    4. 边数 = 图的顶点数量-1


    先看主要代码,再看库代码

    //Prim算法的 最小生成树
    //时间 ElogE                              E为遍历原图中每条边,logE为优先队列(二叉堆)找到最小权重边的平均成本
    //空间 V-1条Edge + V个顶点
    public class LazyPrimMST implements MST {
        EdgeWeightedGraph orgEwg;    //原始加权图
        EdgeWeightedGraph singleEwg; //只有最小生成树的加权图
        List<Edge> edges;            //最小生成树的边
        boolean[] marked;            //顶点的访问
        float weightSum;
    
        public LazyPrimMST(EdgeWeightedGraph ewg) {
            this.orgEwg = ewg;
            edges = new LinkedList<>();
            marked = new boolean[ewg.v];
            weightSum = 0;
    
            //只考虑一个连通图的情况
            //改进:排除有多个子图的情况
            //假设 ewg 是连通的
    
            BinHeap2<Edge> pqedges = new BinHeap2<Edge>();
            //最小生成树的性质:边数 = 图的顶点数量-1
    
            visit2(pqedges, 0);
            while (!pqedges.isEmpty()) {
                Edge e = pqedges.pop();
                int v = e.either();
                int w = e.other(v);
                if (marked[v] && marked[w])                      //已失效的横切边不处理
                    continue;
                edges.add(e);                                    //将权重最小的加入到MST中
                if (!marked[v]) visit2(pqedges, v);
                if (!marked[w]) visit2(pqedges, w);
            }
    
            //将找到的最小生成树转换为一个 EWG对象
            singleEwg = new EdgeWeightedGraph(orgEwg.v());
            for (Edge e : edges) {
                singleEwg.addEdge(e);
            }
            this.orgEwg = null;
        }
    
        private void visit2(BinHeap2<Edge> pqedges, int v) {
            marked[v] = true;                                   //将访问顶点加入MST中
            for (Edge e : orgEwg.adj(v))
                if (!marked[e.other(v)]) pqedges.add(e);
        }
    
        //1.将关注顶点 周围的边加入 横切边集合
        //2.找到横切边集合中权重最小的边
        //3.将改边的对面顶点作为下一个关注顶点返回
        private int visit(BinHeap2<Edge> pqedges, int v) {
            marked[v] = true;                                   //将当前关注点加入最小生成树
            for (Edge e : orgEwg.adj(v)) {                      //加入关注顶点的边到优先队列, 横切边集合
                if (!marked[e.other(v)])                        //只加入未失效的横切边
                    pqedges.add(e);
            }
    
            Edge tmpe = null;
            while (tmpe == null && !pqedges.isEmpty()) {
                tmpe = pqedges.pop();
                if (marked[tmpe.either()] && marked[tmpe.other(tmpe.either())])
                    tmpe = null;                                //失效的横切边
            }
            if (tmpe == null)                                   //没有足够的边
                return -1;
    
            edges.add(tmpe);                                    //将最小权重的边加入到最小生成树
            if (!marked[tmpe.either()])                         //从最小权重的边里,找到未探索的对面顶点作为新的关注点
                v = tmpe.either();
            else
                v = tmpe.other(tmpe.either());
            return v;
        }
    
        @Override
        public Iterable<Edge> edges() {
            return edges;
        }
    
        @Override
        public float weight() {
            if (weightSum == 0) {
                for (Edge e : edges) {
                    weightSum += e.weight();
                }
            }
            return weightSum;
        }
    
        public EdgeWeightedGraph getSingleEWGraph() { //只保留最小生成树的加权有向图
            return singleEwg;
        }
    
        public static void main(String[] args) {
            // 村口         二狗子家
            // 0--------------1
            // |            /|
            // |    你家    / |
            // |  -----2----  |
            // |              |
            // +---------3----+
            //          希望小学
    
            EdgeWeightedGraph ewg = new EdgeWeightedGraph(4);
            ewg.addEdge(0, 1, 2,"二麻二麻路");
            ewg.addEdge(0, 2, 3,"挨打巷西段");
            ewg.addEdge(0, 3, 4,"挨打巷东段");
            ewg.addEdge(1, 2, 3.5f,"恶犬巷");
            ewg.addEdge(1, 3, 2.5f,"希望之路");
            System.out.println(ewg);
            System.out.println("=======");
    
            LazyPrimMST lp = new LazyPrimMST(ewg);
            System.out.println("最小生成树权重总和(村里主干道总长度): " + lp.weight());
            for (Edge e : lp.edges()) {
                System.out.println(e.either() + "和" + e.other(e.either()) + "之间的路["+e.name+"], 路长:" + e.weight());
            }
        }
    }

    输出

    最小生成树权重总和(村里主干道总长度): 7.5
    0和1之间的路[二麻二麻路], 路长:2.0
    1和3之间的路[希望之路], 路长:2.5
    0和2之间的路[挨打巷西段], 路长:3.0

    即时版

    主要使用了带索引的优先队列IndexMinPQ,支持替换操作(change)

    并且对已失效的边用不加入优先队列,以及替换操作减少对失效横切边在优先队列中的排序操作(将时间复杂度从 ElogE 降为 ElogV,因为优先队列里同时只存有最多V-1个边)

    代码:

    //即时prim算法 可对加权无向图生成最小生成树
    //时间 ElogV
    //空间 3V+(V-1)
    //允许平行边,自环(不起作用?),负权重
    public class PrimMst implements Mst {
        EdgeWeightedGraph ewg;
        boolean[] marked;                       //记录已经被访问的,加入到最小生成树中的顶点;标记为true说明其edges 和 weights 被纳入最小生成树
        float[] weights;                        //从最小生成树到顶点v的最小权重边的权重值
        IndexMinPQ<Float> impq;                 //小根索引二叉堆,记录最多V-1个,从最小生成树到目标顶点的边的权重;index:目标顶点索引,val:最小权重边的权重
        Edge[] edges;                           //属于最小生成树中的边的集合,有V-1条
        float weightSum = Float.POSITIVE_INFINITY;//最小生成树的边的权重总和
    
        public PrimMst(EdgeWeightedGraph ewg) {
            this.ewg = ewg;
            marked = new boolean[ewg.v()];
            weights = new float[ewg.v()];
            edges = new Edge[ewg.v()];           //最小生成树中最多有 v-1 条边,为了方便直接用顶点号做下标索引号直接申请v个元素(0号下标的边不会被更新)
            impq = new IndexMinPQ<>(ewg.v());    //每个顶点对应一条最小生成树到它的边
            Arrays.fill(weights, Float.POSITIVE_INFINITY);
    
            visit(0, 0);              //从0开始访问
            while (!impq.isEmpty()) {            //从优先队列中取出权重最小的边,以及从最小生成树通过该边所到达的顶点号
                visit(impq.topIndex() - 1, impq.delTop());
            }
            this.ewg = null;
        }
    
        //对顶点v进行访问,并给出从最小生成树到达该顶点的边的最小权重
        private void visit(int v, float weight) {
            marked[v] = true;
            weights[v] = weight;
            for (Edge e : ewg.adj(v)) {
                int w = e.other(v);
                if (marked[w] || weights[w] < e.weight())   //已经在mst中,则跳过 或 未在mst中,但存在另一条到达该点的权重更小的边 也跳过(contain)
                    continue;
                if (impq.contain(w + 1))                 //优先队列中存在到达该点的边,将权重更新为更小的
                    impq.change(w + 1, e.weight());
                else
                    impq.insert(w + 1, e.weight());      //首次遇到该顶点,将到达该顶点的边的权重和顶点号加入优先队列
                edges[w] = e;
                weights[w] = e.weight();
            }
        }
    
        @Override
        public Iterable<Edge> edges() {
            List<Edge> list = new LinkedList<>();
            for (int v = 1; v < edges.length; v++) {
                list.add(edges[v]);
            }
            return list;
        }
    
        @Override
        public float weight() {
            if (weightSum == Float.POSITIVE_INFINITY && edges.length > 0) {
                weightSum = 0;
                for (float f : weights) {
                    weightSum += f;
                }
            }
            return weightSum;
        }
    
        public static void main(String[] args) {
            demo2();
        }
    
        private static void demo2() {
            // 村口         二狗子家
            // 0--------------1
            // |            /|
            // |    你家    / |
            // |  -----2----  |
            // |              |
            // +---------3----+
            //          希望小学
    
            EdgeWeightedGraph ewg = new EdgeWeightedGraph(4);
            ewg.addEdge(0, 1, 2, "二麻二麻路");
            ewg.addEdge(0, 2, 3, "挨打巷西段");
            ewg.addEdge(0, 3, 4, "挨打巷东段");
            ewg.addEdge(1, 2, 3.5f, "恶犬巷");
            ewg.addEdge(1, 3, 2.5f, "希望之路");
            System.out.println(ewg);
            System.out.println("=======");
    
            PrimMst lp = new PrimMst(ewg);
            System.out.println("最小生成树权重总和(村里主干道总长度): " + lp.weight());
            for (Edge e : lp.edges()) {
                System.out.println(e.either() + "和" + e.other(e.either()) + "之间的路[" + e.name + "], 路长:" + e.weight());
            }
            System.out.println("
    =======");
        }
    }

    输出:

    =======
    最小生成树权重总和(村里主干道总长度): 7.5
    0和1之间的路[二麻二麻路], 路长:2.0
    0和2之间的路[挨打巷西段], 路长:3.0
    1和3之间的路[希望之路], 路长:2.5
    =======

    库代码: 

    //加权无向图
    public class EdgeWeightedGraph {
        LinkedList<Edge>[] edges;               //边的集合
        final int v;                            //顶点数量
        int e;                                  //边的数量
    
        public EdgeWeightedGraph(int v) {
            this.v = v;
            this.e = 0;
            edges = new LinkedList[v];
            for (int i = 0; i < v; i++)
                edges[i] = new LinkedList<>();
        }
    
        public void addEdge(int v, int w, float weight) {
            addEdge(new Edge(v, w, weight));
        }
    
        public void addEdge(Edge e) {           //添加一条边,在无向图中等于向2边顶点添加边(互相连通)
            edges[e.v].add(e);
            edges[e.w].add(e);
            this.e++;
        }
    
        public Iterable<Edge> adj(int v) {
            return edges[v];
        }
    
        //返回所有边的集合
        public Iterable<Edge> edges() {
            List<Edge> es = new LinkedList<>();
            for (int v = 0; v < edges.length; v++) {
                for (Edge e : adj(v)) {
                    if (e.other(v) > v)           //加入顺序: 顶点序号逆序
                        es.add(e);
                }
            }
            return es;
        }
    
        public int v() {//顶点数
            return v;
        }
    
        public int e() {//边数
            return e;
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (int v = 0; v < v(); v++) {
                sb.append(v);
                sb.append(": ");
                for (Edge e : adj(v)) {
                    sb.append(e.toString());
                    sb.append(", ");
                }
                sb.append('
    ');
            }
            return sb.toString();
        }
    
        public static void main(String[] args) {
            EdgeWeightedGraph ewg = new EdgeWeightedGraph(4);
            ewg.addEdge(0, 1, 1);
            ewg.addEdge(0, 2, 2);
            ewg.addEdge(0, 3, 3);
            ewg.addEdge(1, 2, 3);
            System.out.println(ewg);
        }
    }

    边对象

    public class Edge extends Vertex implements Comparable<Edge> {
        public int w;
        public float weight;
        public String name;
    
        //from - to
        public Edge(int v, int w, float weight) {
            super(v);
            this.w = w;
            this.weight = weight;
        }
    
        public Edge(int v, int w, float weight, String name) {
            super(v);
            this.w = w;
            this.weight = weight;
            this.name = name;
        }
    
        public float weight() {
            return weight;
        }
    
        public int either() {
            return v;
        }
    
        public int other(int vertex) {
            if (vertex == this.v) return w;
            else if (vertex == this.w) return v;
    
            throw new RuntimeException("no such vertex " + vertex + " ,[v:" + v + " w:" + w + "]");
        }
    
        @Override
        public int compareTo(@NonNull Edge another) {
            if (weight > another.weight)
                return 1;
            else if (weight < another.weight)
                return -1;
            return 0;
        }
    
        @Override
        public String toString() {
            return String.format("%d-%d %.2f", v, w, weight);
        }
    }

    链接: 索引优先队列

    IndexMinPQ

  • 相关阅读:
    python--输出spwm的数组
    爬虫二:爬取糗事百科段子
    爬虫一:爬取信息
    Python中的切片操作
    JSON
    python 中的异常处理与种类
    register的功能
    static的功能
    网络安全的认识
    VMware5.5-vCenter Converter(转换)
  • 原文地址:https://www.cnblogs.com/cyy12/p/11974506.html
Copyright © 2011-2022 走看看