Algorithms Fourth Edition
Written By Robert Sedgewick & Kevin Wayne
Translated By 謝路雲
Chapter 4 Section 3 最小生成樹算法
定義數組
樹是特殊的圖數據結構
圖的生成樹: 含有圖所有頂點的無環連通子圖this
加權無向圖的最小生成樹(MST):權重最小的生成樹spa
約定code
只考慮連通圖:根據生成樹的定義component
邊的權重能夠爲0或者爲負orm
全部邊的權重各不相同:方便證實索引
切分:將圖的頂點集分爲兩個非空而且沒有交集的集合隊列
橫切邊:連接兩個屬於不一樣集合的頂點的邊。(下圖的紅色邊)
在一副加權圖中,給定任意的切分,它的橫切邊中的權重最小者必然屬於圖中的最小生成樹。
將含有V個頂點的任意加權連通圖中屬於最小生成樹的邊標記爲黑色。
初始狀態下全部邊均爲灰色,找到一種切分,它產生的橫切邊均不爲黑色。
將它權重最小的橫切邊標記爲黑色。
反覆,直到標記了V-1條黑色邊爲止。
邊的兩個頂點
權重
權重大小比較
public class Edge implements Comparable<Edge> { private final int v; // one vertex private final int w; // the other vertex private final double weight; // edge weight public Edge(int v, int w, double weight) { 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("Inconsistent edge"); } 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); } }
修改了方法 Iterable<Edge> adj(v) 原來返回的是相連的點,如今返回的是鄰邊
新增了方法 Iterable<Edge> edges() 由於最小生成樹更看重邊的要素
API容許平行邊和自環,可是下面代碼在實現的過程當中並無統計它們,這對最小生成樹並不會產生影響。
public class EdgeWeightedGraph { private final int V; // number of vertices private int E; // number of edges private Bag<Edge>[] adj; // adjacency lists 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(), 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> 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
設圖G頂點集合爲U,
任意選擇圖G中的一點做爲起始點a,將該點加入集合V
再從集合U-V中找到另外一點b使得點b到V中任意一點的權值最小,此時將b點也加入集合V
以此類推,直至全部頂點所有被加入V,此時就構建出了一顆MST。
由於有N個頂點,因此該MST就有N-1條邊,每一次向集合V中加入一個點,就意味着找到一條MST的邊。
頂點: 使用boolean marked[]。若是頂點v在樹中,則marked[v]=true。
邊: 使用隊列來保存最小生成樹的邊 or 由頂點索引的數組edgeTo[]
橫切邊: 使用優先隊列MinPQ<Edge>來根據權重進行比較
將每一條和樹相連的邊加入優先隊列MinPQ<Edge>,依次取出最小的邊,取出後再判斷是不是橫切邊
複雜度
時間:ElogE 最壞狀況下,一次插入成本爲~lgE,刪除最小元素的成本爲~2lgE,最多隻能插入E條邊,刪去E條邊。
空間:E 優先隊列中最多可能有E條邊
public class LazyPrimMST { private boolean[] marked; // MST vertices private Queue<Edge> mst; // MST edges private MinPQ<Edge> pq; // crossing (and ineligible) edges 橫切邊 public LazyPrimMST(EdgeWeightedGraph G) { pq = new MinPQ<Edge>(); marked = new boolean[G.V()]; mst = new Queue<Edge>(); visit(G, 0); // assumes G is connected (see Exercise 4.3.22) while (!pq.isEmpty()) { Edge e = pq.delMin(); // Get lowest-weight int v = e.either(), w = e.other(v); // edge from pq. if (marked[v] && marked[w]) continue; // Skip if ineligible. mst.enqueue(e); // Add edge to tree. if (!marked[v]) visit(G, v); // Add vertex to tree if (!marked[w]) visit(G, w); // (either v or w). } } private void visit(EdgeWeightedGraph G, int v) { // Mark v and add to pq all edges from v unmarked vertices. marked[v] = true; for (Edge e : G.adj(v)) if (!marked[e.other(v)]) pq.insert(e); } public Iterable<Edge> edges() { return mst; } public double weight() // See Exercise 4.3.31. }
將每一個點和樹相連的最小邊維護在數組edgeTo[] 和 distTo[]裏,每向樹加入一個點,更新一次數組
edgeTo[] 記錄頂點/邊; distTo[] 記錄權重
複雜度
時間:ElogV 最壞狀況下,一次插入成本爲~lgV,刪除最小元素的成本爲~2lgV,最多隻能插入E條邊,刪去E條邊。
空間:E 優先隊列中最多可能有E條邊
public class PrimMST { private Edge[] edgeTo; // shortest edge from tree vertex private double[] distTo; // distTo[w] = edgeTo[w].weight() private boolean[] marked; // true if v on tree private IndexMinPQ<Double> pq; // eligible crossing edges 橫切邊 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); // Initialize pq with 0, weight 0. while (!pq.isEmpty()) visit(G, pq.delMin()); // Add closest vertex to tree. } private void visit(EdgeWeightedGraph G, int v) { // Add v to tree; update data structures. marked[v] = true; for (Edge e : G.adj(v)) { int w = e.other(v); if (marked[w]) //v w都在樹裏,不是橫切邊,所以不用更新 continue; if (e.weight() < distTo[w]) { // 是橫切邊,且權重更小,所以更新 edgeTo[w] = e; distTo[w] = e.weight(); if (pq.contains(w)) //頂點已存在,則修改 pq.change(w, distTo[w]); else //頂點第一次出現,則新建 pq.insert(w, distTo[w]); } } } public Iterable<Edge> edges() // See Exercise 4.3.21. public double weight() // See Exercise 4.3.31. }
按照邊的權重順序(從小到大)處理
選擇最小權重的邊,判斷是否會構成環,不會則加入最小生成樹。
循環如此,直至樹中含有V-1條邊爲止。
複雜度
時間:ElogE 初始化優先隊列,最壞狀況E次比較;每次操做成本2lgE次比較,最多還會多E次connected() 和 V次union()操做,但這些成本相比ElogE的增加數量級可忽略不計(詳見1.5)
空間:E
public class KruskalMST { private Queue<Edge> mst; public KruskalMST(EdgeWeightedGraph G) { mst = new Queue<Edge>(); MinPQ<Edge> pq = new MinPQ<Edge>(G.edges()); UF uf = new UF(G.V()); //Reference: Union Find in Chapter 1 while (!pq.isEmpty() && mst.size() < G.V() - 1) { Edge e = pq.delMin(); // Get min weight edge on pq int v = e.either(), w = e.other(v); // and its vertices. if (uf.connected(v, w)) continue; // Ignore ineligible edges. uf.union(v, w); // Merge components. mst.enqueue(e); // Add edge to mst. } } public Iterable<Edge> edges(){ return mst; } public double weight() // See Exercise 4.3.31. }