最小生成樹

一.概述算法

      加權無向圖是一種在無向圖的基礎上,爲每條邊關聯一個權值或是成本的圖模型.應用能夠有不少:例如在一幅航空圖中,邊表示導線,權值則表示導線的長度或是成本等.數組

  圖的生成樹是它的一顆含有其全部頂點的無環連通子圖,一幅加權圖的最小生成樹(MST)是它的一顆權值(樹中的全部邊的權值之和)最小的生成樹.下圖爲一幅加權無向圖和它的最小生成樹.(箭頭不指示方向,標紅的爲最小生成樹).數據結構

                                                                                 

二.原理ide

  1.圖的一種切分是將圖的全部頂點分爲兩個非空且不重疊的兩個集合.橫切邊是鏈接兩個屬於不一樣集合的頂點的邊.oop

    2.切分定理:在一幅加權圖中,給定任意的切分,它的橫切邊中的權重最小者必然屬於最小生成樹.(假設命題不成立,假設權重最小的邊爲e,將它加入最小生成樹必然就會成環,那麼這個環應該還有另外一條橫切邊,而該橫切邊f必然權重>e,那麼就能夠刪除f,獲得e,來獲取權重更小的生成樹.就和最小生成樹矛盾,所以命題成立).ui

三.貪心算法this

      初始狀態下,一個含有V個頂點的無向圖全部的邊都不爲黑色,找到一種切分,它所產生的橫切邊均不爲黑色,橫切邊中權值最小的邊標記爲黑色(放入最小生成樹),如此往復,直到標記了V-1條邊.(根據切分定理很容易證實),圖的貪心算法的執行流程以下,箭頭不指示方向,黑色加粗的邊爲最小生成樹的邊.spa

                          

                          

                            

                            

                            

                              

                              

                             

四.加權無向圖的數據類型表示.orm

 1.帶權重的邊的數據類型:blog

  邊的數據類型記錄了邊的兩點,而且給出了獲取邊其中的某一點,以及根據某點獲取另一個點的方法.同時也用了一個weight的變量來記錄邊的權重.邊的數據結構以下:

複製代碼
//帶權重的邊的數據類型
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) {
        super();
        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 RuntimeException("Inconsisitent edge");
    }
    @Override
    public int compareTo(Edge that) {
        if(this.weight<that.weight) return -1;
        else if(this.weight>that.weight) return 1;
        else return 0;
    }
    public String toString() {
        return String.format("%d%-d %.2f", v,w,weight);
    }
    
}
複製代碼

2.加權無向圖的數據類型:

  這個數據類型和Graph的API基本類似,不一樣的是,這個API的基礎是Edge且添加了一個edges方法.加權無向圖的代碼以下:

複製代碼
public class EdgeWeightedGraph {
    private final int V;    //頂點總數
    private int E;            //邊的總數
    private Bag<Edge> [] adj;    //鄰接表
    
    public EdgeWeightedGraph(int V) {
        this.V=V;
        this.E=0;
        adj=(Bag<Edge> [])new Bag[V];//和Queue不一樣,Bag保證無序
        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) {
        return adj[v];
    }
     public Iterable<Edge> edges() {
            Bag<Edge> list = new Bag<Edge>();
            for (int v = 0; v < V; v++) {
                int selfLoops = 0;
                for (Edge e : adj(v)) {
                    if (e.other(v) > v) {
                        list.add(e);
                    }
                    else if (e.other(v) == v) {
                        if (selfLoops % 2 == 0) list.add(e);
                        selfLoops++;
                    }
                }
            }
            return list;
        }
}
複製代碼

五.Prim算法

   Prim算法爲計算最小生成樹提供了實現.它的每一步都爲樹添加一條邊.每一次都將下一條鏈接樹中的頂點和不在樹中的頂點且權重最小的邊加入樹中.代碼以下所示:

複製代碼
//最小生成樹的延遲Prim算法
/**
 * 先對樹上的第0個點進行標記.
 * 而後遍歷第0個點的全部相鄰頂點.
 * 若是沒有被標記的話.那麼就把對應的邊添加進去MinPQ
 * 在MinPQ中移除權重最小的(若是兩個頂點都被標記的話,那麼直接繼續)
 * 沒有被標記的話,直接visit,標記,而後添加到MinPQ中
 * @author Administrator
 *
 */
public class LazyPrimMST {
    private boolean[] marked;    //最小生成樹的頂點
    private Queue<Edge> mst;    //最小生成樹的邊.
    private MinPQ<Edge> pq;     //橫切邊(其中包括失效的邊)
    public LazyPrimMST(EdgeWeightedGraph G) {
        pq=new MinPQ<Edge>();
        marked=new boolean[G.V()];
        mst=new Queue<Edge>();
        visit(G,0);        //假設G是連通的
        while(!pq.isEmpty()) {
            Edge e=pq.delMin();
            int v=e.either();
            int w=e.other(v);
            if(marked[v]&&marked[w]) continue;    //忽略無效邊
            mst.enqueue(e);                //將權重最小的邊加入最小生成樹
            if(!marked[v]) visit(G,v);    //標記將點加入到集合
            if(!marked[w]) visit(G,w);
        }
    }
    private void visit(EdgeWeightedGraph G, int v) {
        //標記頂點v並將全部鏈接v和未被標記的頂點的邊加入pq
        marked[v]=true;
        for(Edge e:G.adj(v)) {
            if(!marked[e.other(v)]) pq.insert(e);
        }
    }
    public Iterable<Edge> edges() {
        return mst;
    }
}
複製代碼

  之因此稱爲延遲算法,這是由於MinPQ中保存了大量的無效邊.

六.Prim算法的即時實現

   Prim算法的即時實現遵循下面的思路:只會在優先隊列中保存每一個非樹頂點w的一條邊.採用索引優先隊列,將頂點和邊關聯起來.該邊爲將它與樹中的頂點鏈接起來的權重最小的邊.

  PrimMST類有兩個數組edgeTo,distTo,他們具備如下性質:

  1.若是頂點v不在樹中但至少含有一條邊與樹相鏈接,那麼edgeTo[v]是與樹相鏈接的權重最小的邊,distTo[v]是該邊的權重.

  2.這類點都存放在索引優先隊列中.

  該算法的基本步驟是PrimMST會從優先隊列中取出一個頂點v,而且檢查與它相連的點v-w,若是w被標記過,則失效,若是w不在優先隊列中或者v-w權重小於目前已知的最小值edgeTo[w],代碼會更新數組,將v-w做爲樹的最佳選擇.代碼以下:

 

複製代碼
/**
 * 先將0加入最小索引隊列中.而後經過對於G.adj(v)進行遍歷.
 * 得到不一樣的邊,觀察頂點鏈接到樹的邊是否小於distTo保存的數值.
 * 若是小於的話,那麼添加進去.或者修改.
 * 不小於不變.一個個點進行遍歷.不斷得到各個點到樹的最小的距離.
 * 而後將距離最小的點加進去
 * 
 * @author Administrator
 *
 */
public class PrimMST {
    private Edge[] edgeTo;                //距離樹最近的邊
    private double[] distTo;            //distTo[w]=edgeTo[w].weight()
    private boolean[] marked;            //若是v在樹中則爲true
    private IndexMinPQ<Double> pq;        //有效的橫切邊
    
    public PrimMST(EdgeWeightedGraph G) {
        edgeTo=new Edge[G.V()];
        distTo=new double[G.V()];
        marked=new boolean[G.V()];
        for(int v=0;v<G.V();v++) {
            distTo[v]=Double.POSITIVE_INFINITY;
        }
        pq=new IndexMinPQ<Double>(G.V());
        distTo[0]=0.0;
        pq.insert(0, 0.0);
        while(!pq.isEmpty()) {
            visit(G,pq.delMin());//每次都取出權重最小的邊
        }
    }

    private void visit(EdgeWeightedGraph G, int v) {
        marked[v]=true;
        for(Edge e:G.adj(v)) {
            int w=e.other(v);
            if(marked[w]) continue;    //v-w失效
            if(e.weight()<distTo[w]) {
                edgeTo[w]=e;
                distTo[w]=e.weight();
                if(pq.contains(w)) pq.changeKey(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;
        }

}
複製代碼

七.Kruskal算法

  Kruskal算法的思想按照邊的權重順序從小到大處理它們,而後將邊按照從小到大的次序加入到最小生成樹中,加入的邊不會和已經加入的邊構成環,直到樹中含有v-1條邊爲止.原理在於若是加入的邊不會和已有的邊構成環,那麼它就是一個跨越了全部和樹頂點相鄰的頂點組成的集合以及他們的補集所構成的切分.而這個切分的權重又是最小的,根據切分定理,它必定是最小生成樹的一條邊.

  代碼的實現以下所示,代碼中用了UF判斷是否構成環,而且將一個兩個點相連,使之共同構成在同一個連通份量.

複製代碼
/**
 * 首先將全部的邊都加入到MinPQ中.
 * 而後根據權值從小到大依次加入.
 * 若是該邊已經在最小生成樹裏,就忽略.
 * 若是不在,那麼就加入進去(union方法).直到mst的size達到v-1
 * 原則:若是下一條被加入的邊不會和已有的造成環.那麼它
 * 就至關因而一個切分,而它的權重又是最小的.所以能夠加入進去
 * @author Administrator
 *
 */
public class KruskalMST {
    private Queue<Edge> mst;
    public KruskalMST(EdgeWeightedGraph G) {
        mst=new Queue<Edge>();
        MinPQ<Edge> pq=new MinPQ<>();
        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)) continue;    //處於同一個連通份量中.
            uf.union(v, w);
            mst.enqueue(e);
        }
    }
    public Iterable<Edge> edges() {
        return mst;
    }
    
}
複製代碼
複製代碼
public class UF {
    private int[] id;        //份量id
    private int count;        //份量數量
    public UF(int N) {
        count=N;
        id=new int[N];
        for(int i=0;i<N;i++) {
            id[i]=i;
        }
    }
    //連通份量的數量
    public int count() {
        return count;
    }
    //若是pq存在於同一個連通份量中,則返回true
    public boolean connected(int p,int q) {
        return find(p)==find(q);
    }
    //p所在的連通份量標識符
    public int find(int p) {
        // TODO Auto-generated method stub
        return id[p];
    }
    //在p,q之間創建鏈接(QuickFind算法)
    public void union(int p,int q) {
        //將p和q歸併到相同的份量中
        int pID=find(p);
        int qID=find(q);
        //若是已經在相同的份量中則不採起行動
        if(pID==qID) return ;
        for(int i=0;i<id.length;i++) {
            if(id[i]==pID) id[i]=qID;
        }
        count--;
    }
}
複製代碼
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息