加权无向图 最小生成树 Prim算法 延迟版和即时版 村里修路该先修哪

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

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

 

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

 

最小生成树的特点

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

2. 是一颗树(无环)

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

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


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

 

//Prim算法的 最小生成树
//时间 ElogE                              E为遍历原图中每条边,logE为优先队列(二叉堆)找到最小权重边的平均成本
//空间 V-1条Edge + V个顶点
public class LazyPrimMST implements MST {
    EdgeWeightedGraph orgEwg;    //原始加权图
    EdgeWeightedGraph singleEwg; //只有最小生成树的加权图
    List edges;            //最小生成树的边
    boolean[] marked;            //顶点的访问
    float weightSum;

    public LazyPrimMST(EdgeWeightedGraph ewg) {
        this.orgEwg = ewg;
        edges = new LinkedList<>();
        marked = new boolean[ewg.v];
        weightSum = 0;

        //只考虑一个连通图的情况
        //改进:排除有多个子图的情况
        //假设 ewg 是连通的

        BinHeap2 pqedges = new BinHeap2();
        //最小生成树的性质:边数 = 图的顶点数量-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 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 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 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 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 edges() {
        List 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("\n=======");
    }
}

 

输出:

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

 

 

库代码: 

 

//加权无向图
public class EdgeWeightedGraph {
    LinkedList[] 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 adj(int v) {
        return edges[v];
    }

    //返回所有边的集合
    public Iterable edges() {
        List 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('\n');
        }
        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 {
    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

 

你可能感兴趣的:(加权无向图 最小生成树 Prim算法 延迟版和即时版 村里修路该先修哪)