加權無向圖的實現最簡單的方法是擴展無向圖的表示方法:在鄰接表的表示中,能夠在鏈表的結點中增長一個權重域。但這裏用另外一個方法來實現:咱們實現兩個類,權重邊類和無向圖類。無向圖類中組合權重邊類來實現加權無向圖。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; } }
圖的生成樹是它的一棵含有其全部頂點的無環連通子圖,加權圖的最小生成樹(MST)是它的一棵權值最小的生成樹。 Prim算法可以獲得任意加權連通無向圖的最小生成樹。算法
圖的一種切分是將圖的全部頂點分爲兩個非空且不重合的兩個集合。橫切邊是一條鏈接兩個屬於不一樣集合的頂點的邊。數組
切分定理:在一幅加權圖中,給定任意的切分,它橫切邊中權重最小者必然屬於圖的最小生成樹。數據結構
切分定理是解決最小生成樹問題的全部算法的基礎。this
使用一個最小優先權隊列保存橫切邊集合,每次新加進來一個結點,就將和該結點關聯的全部邊添加進最小優先權隊列;生成最小樹時,從橫切邊集合中取出最小邊,判斷是否和目前的樹產生環,若是產生環,則捨棄該邊;不然將該邊加入最小生成樹隊列。spa
延時實現比較簡單,它會在優先權隊列中保存已經失效的邊。.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
要改進LazyPrimMST,能夠嘗試從優先隊列中刪除用不到的邊。關鍵在於,咱們關注的只是鏈接樹頂點和非樹頂點中權重最小的邊。當咱們將頂點v加入樹中,只可能使非樹頂點w到最小生成樹更近了。簡而言之,咱們沒必要保存全部從w到樹頂點的邊, 只需保存最小的那條便可。在v添加進樹中時遍歷v的鄰接表檢查是否須要更新權重最小的邊。orm
引進兩個頂點索引數組edgeTo[]和distTo[],它們有以下性質:blog
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算法的計算一個含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; } }