加權無向圖問題--最小代價生成樹(Prim算法、kruskal算法)

加權無向圖的實現

加權無向圖的實現最簡單的方法是擴展無向圖的表示方法:在鄰接表的表示中,能夠在鏈表的結點中增長一個權重域。但這裏用另外一個方法來實現:咱們實現兩個類,權重邊類和無向圖類。無向圖類中組合權重邊類來實現加權無向圖。java

權重邊類:

public class Edge implements Comparable<Edge> {    //實現Comparable接口
    private int v;
    private int w;
    private double weight;
	
    public Edge(int v,int w,double weight)
    { this.v = v; this.w = w; this.weight = weight;}

	//獲取邊的一個結點和另外一個節點
    public int either() {return v;}
    public int other(int vertex) {
        if(vertex == v) return w;
        else if(vertex == w) return v;
    }
    //實現接口中的compareTo()方法
    public int compareTo(Edge that) {
        if(this.weight() < that.weight()) return -1;
        else if(this.weight() > that.weight()) return 1;
        else return 0;
    }

    public double weight() {return weight;}
    public String toString() { return String.format("%d-%d %.2f", v,w,weight); }
}

加權無向圖類:

public class EdgeWeightedGraph {
    private 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];
        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);
        //由於是無向圖,互相添加邊,調用的是揹包的add()方法
        adj[v].add(e);   
        adj[w].add(e);
        E++;
    }
	//和v相關聯的全部邊
    public Iterable<Edge> adj(int v){ return adj[v]; }
    //圖的全部邊
    public Iterable<Edge> edges(){
        Bag<Edge> b = new Bag<Edge>();
        for(int v = 0; v <V ; v++)
            for(Edge e : adj[v])
                if(e.other(v)>v) b.add(e);
        return b;
    }
}

Prim算法:

圖的生成樹是它的一棵含有其全部頂點的無環連通子圖,加權圖的最小生成樹(MST)是它的一棵權值最小的生成樹。 Prim算法可以獲得任意加權連通無向圖的最小生成樹。算法

切分:

圖的一種切分是將圖的全部頂點分爲兩個非空且不重合的兩個集合。橫切邊是一條鏈接兩個屬於不一樣集合的頂點的邊。數組

切分定理:在一幅加權圖中,給定任意的切分,它橫切邊中權重最小者必然屬於圖的最小生成樹。數據結構

切分定理是解決最小生成樹問題的全部算法的基礎。this

數據結構:

  • 採用一個布爾數組marked[]來記錄頂點是否在樹中,若是頂點v在,則marked[v]爲true。
  • 使用一條優先權隊列來保存全部的橫切邊。
  • 使用一條隊列保存最小生成樹的邊。

算法思想:

使用一個最小優先權隊列保存橫切邊集合,每次新加進來一個結點,就將和該結點關聯的全部邊添加進最小優先權隊列;生成最小樹時,從橫切邊集合中取出最小邊,判斷是否和目前的樹產生環,若是產生環,則捨棄該邊;不然將該邊加入最小生成樹隊列。spa

Prim算法延時實現:

延時實現比較簡單,它會在優先權隊列中保存已經失效的邊。.net

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);//從頂點0 開始
        while(!pq.isEmpty()) {//構造最小生成樹
            Edge e = pq.delMin();
            int v = e.either(),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) {//更新橫切邊集合
        marked[v] = true;
        for(Edge e:G.adj(v))
            if(!marked[e.other(v)])    pq.insert(e);
    }
    public Iterable<Edge> edges(){//返回最小生成樹
        return mst;
    }
}

Prim算法的延時實現計算一個含V個頂點和E條邊的連通加權無向圖的最小生成樹所需空間與E成正比,所需時間與ElogE成正比(最壞狀況)。code

Prim算法即時實現:

要改進LazyPrimMST,能夠嘗試從優先隊列中刪除用不到的邊。關鍵在於,咱們關注的只是鏈接樹頂點和非樹頂點中權重最小的邊。當咱們將頂點v加入樹中,只可能使非樹頂點w到最小生成樹更近了。簡而言之,咱們沒必要保存全部從w到樹頂點的邊, 只需保存最小的那條便可。在v添加進樹中時遍歷v的鄰接表檢查是否須要更新權重最小的邊。orm

引進兩個頂點索引數組edgeTo[]和distTo[],它們有以下性質:blog

  • 若是頂點v不在樹中但至少含有一條邊和樹相連,那麼edgeTo[v]將是v和樹鏈接的最短的邊,distTo[v]爲這條邊的權重。
  • 全部這類v都保存在一條索引優先隊列中,索引v關聯的值是edgeTo[v]的邊的權重。
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);//頂點0初始化pq
        while(!pq.isEmpty())
            visit(G,pq.delMin());
        }
	
    public 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;//最佳邊Edge變爲e
                distTo[w] = e.weight();//更新distTo[]
                if(pq.contains(w))    pq.change(w, distTo[w]);
                else pq.insert(w, distTo[w]);
            }
        }
    }
}

Prim算法的即時實現計算一個含有V個頂點和E條邊的連通加權無向圖的最小生成樹所需空間和V成正比,所需時間和ElogV成正比(最壞狀況)。

Kruskal算法:

數據結構:

  • 用一條優先隊列將邊按照權重從小到大排序
  • union-find算法來識別會造成環的邊
  • 用一條隊列來保存最小生成樹的全部邊

Kruskal算法的計算一個含V個頂點和E條邊的連通加權無向圖的最小生成樹所需空間與E成正比,所需時間與ElogE成正比(最壞狀況)。

算法思想:

將邊都添加進最小優先權隊列中,每次從中取出最小的邊,檢查會不會與已經選出的邊構成環(使用union-find算法),若是構成環,則棄掉這條邊,不然將這條邊加入最小生成樹隊列。循環執行直到最小優先權隊列爲空。

算法實現:

public class KruskalMST {
    private Queue<Edge> mst;    //用來保存最小代價生成樹的隊列
	
    public KruskalMST(EdgeWeightedGraph G) {
        mst = new Queue<Edge>();
        MinPQ<Edge> pq = new MinPQ<Edge>();    //最小優先權隊列
        for(Edge e: G.edges())
            pq.insert(e);//將全部邊添加進優先隊列
        UF uf = new UF(G.V());    //union-find算法
		
        while(!pq.isEmpty() && mst.size()<G.V()-1) {
            Edge e = pq.delMin();//從優先隊列獲得最小的邊
            int v = e.either(),w = e.other(v);//獲得最小邊的頂點
            if(uf.connected(v, w))	continue;//判斷會不會構成環
            uf.qu_union(v,w);//合併份量
            mst.enqueue(e);//將邊添加進樹中
        }
    }
    public Iterable<Edge> edges(){ return mst; }
}
相關文章
相關標籤/搜索