算法(第4版) Chapter 4.3 最小生成樹

Algorithms Fourth Edition
Written By Robert Sedgewick & Kevin Wayne
Translated By 謝路雲
Chapter 4 Section 3 最小生成樹算法


定義數組

  • 樹是特殊的圖數據結構

  • 圖的生成樹: 含有圖所有頂點的無環連通子圖this

  • 加權無向圖的最小生成樹(MST):權重最小的生成樹spa

約定code

  • 只考慮連通圖:根據生成樹的定義component

  • 邊的權重能夠爲0或者爲負orm

  • 全部邊的權重各不相同:方便證實索引

原理

切分定理

  • 切分:將圖的頂點集分爲兩個非空而且沒有交集的集合隊列

  • 橫切邊:連接兩個屬於不一樣集合的頂點的邊。(下圖的紅色邊)
    橫切邊

  • 在一副加權圖中,給定任意的切分,它的橫切邊中的權重最小者必然屬於圖中的最小生成樹

貪心算法

  • 將含有V個頂點的任意加權連通圖中屬於最小生成樹的邊標記爲黑色。

  1. 初始狀態下全部邊均爲灰色,找到一種切分,它產生的橫切邊均不爲黑色。

  2. 將它權重最小的橫切邊標記爲黑色。

  3. 反覆,直到標記了V-1條黑色邊爲止。

加權無向圖

加權邊API

加權邊

  • 邊的兩個頂點

  • 權重

  • 權重大小比較

Edge 代碼

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);
    }
}

加權無向圖API

加權無向圖

  • 修改了方法 Iterable<Edge> adj(v) 原來返回的是相連的點,如今返回的是鄰邊

  • 新增了方法 Iterable<Edge> edges() 由於最小生成樹更看重邊的要素

EdgeWeightedGraph 代碼

  • 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;
    }
}

最小生成樹API

最小生成樹API

Prim算法

  • 從點的方面考慮構建一顆MST

  • 設圖G頂點集合爲U,

  1. 任意選擇圖G中的一點做爲起始點a,將該點加入集合V

  2. 再從集合U-V中找到另外一點b使得點b到V中任意一點的權值最小,此時將b點也加入集合V

  3. 以此類推,直至全部頂點所有被加入V,此時就構建出了一顆MST。

  • 由於有N個頂點,因此該MST就有N-1條邊,每一次向集合V中加入一個點,就意味着找到一條MST的邊。

數據結構

  • 頂點: 使用boolean marked[]。若是頂點v在樹中,則marked[v]=true。

  • 邊: 使用隊列來保存最小生成樹的邊 or 由頂點索引的數組edgeTo[]

  • 橫切邊: 使用優先隊列MinPQ<Edge>來根據權重進行比較

延時Prim圖示

  • 將每一條和樹相連的邊加入優先隊列MinPQ<Edge>,依次取出最小的邊,取出後再判斷是不是橫切邊
    維護橫切邊

LazyPrimMST 代碼

  • 複雜度

    • 時間: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.
}

即時Prim圖示

  • 將每一個點和樹相連的最小邊維護在數組edgeTo[] 和 distTo[]裏,每向樹加入一個點,更新一次數組

  • edgeTo[] 記錄頂點/邊; distTo[] 記錄權重
    即時Prim圖示

PrimMST 代碼

  • 複雜度

    • 時間: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.
}

Kruskal算法

  • 按照邊的權重順序(從小到大)處理

  1. 選擇最小權重的邊,判斷是否會構成環,不會則加入最小生成樹。

  2. 循環如此,直至樹中含有V-1條邊爲止。

Kruskal算法圖示

Kruskal算法

KruskalMST 代碼

  • 複雜度

    • 時間: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.
}
相關文章
相關標籤/搜索