最小生成樹:貪心算法的經典應用,Prim算法入門

一、什麼是生成樹和最小生成樹?

在數據結構中,圖是由頂點(也叫節點)和邊組成的結構。生成樹是指連通無向圖的一個子圖,它包含圖中所有的頂點,並且沒有環(即樹的性質:n個頂點有n-1條邊,且任意兩個頂點之間有且僅有一條路徑)。

舉個例子:如果有4個城市(頂點)A、B、C、D,它們之間的道路(邊)連接起來,形成一個沒有重複道路和閉環的網絡,這就是一個生成樹。而最小生成樹(Minimum Spanning Tree, MST) 則是所有可能的生成樹中,邊權值之和最小的那一棵。

比如,城市間的道路有不同的長度(權值),我們希望找到連接所有城市且總長度最短的道路網絡,這就是最小生成樹的問題。

二、貪心算法:爲什麼最小生成樹適合貪心?

貪心算法的核心思想是“每一步都選擇當前最優解,最終得到全局最優解”。最小生成樹恰好符合貪心算法的適用場景,因爲:

  • 生成樹必須包含所有頂點,且邊數爲n-1(n爲頂點數)。
  • 我們只需要考慮“局部最優”的邊選擇:每次選擇連接“已選頂點集合”和“未選頂點集合”的最小權邊,就能逐步構建出總權值最小的生成樹。

三、Prim算法的基本思路

Prim算法是最小生成樹的經典貪心算法之一,核心步驟如下:

  1. 初始化:任選一個頂點作爲起點(例如頂點v₀),將其標記爲“已選頂點”。
  2. 重複擴展:每次從“已選頂點集合”和“未選頂點集合”之間的所有邊中,選擇權值最小的那條邊,將對應的未選頂點加入“已選頂點集合”。
  3. 終止條件:當所有頂點都被加入“已選頂點集合”時,生成樹構建完成。

四、算法步驟詳解(附實例)

爲了直觀理解,我們用一個簡單的例子演示Prim算法:

示例圖:有4個頂點A、B、C、D,邊及其權值如下:
- A-B: 2,A-C: 3,B-C: 1,B-D: 4,C-D: 5

頂點編號:A=0,B=1,C=2,D=3。

步驟1:選擇起點

任選一個起點,比如選A(頂點0)。此時:
- 已選頂點集合 S = {0}(僅包含A)
- 未選頂點集合 U = {1, 2, 3}(B、C、D)

步驟2:尋找最小邊並擴展

重複以下操作,直到所有頂點被選中:

第一次迭代:

S = {0},U = {1, 2, 3}
需要找S(0)和U(1,2,3)之間的所有邊:
- A-B(0-1): 權值2
- A-C(0-2): 權值3
最小邊是A-B(權值2)。將B(1)加入S:
S = {0, 1},U = {2, 3}

第二次迭代:

S = {0, 1},U = {2, 3}
需要找S(0,1)和U(2,3)之間的所有邊:
- A-C(0-2): 權值3
- B-C(1-2): 權值1
- B-D(1-3): 權值4
最小邊是B-C(權值1)。將C(2)加入S:
S = {0, 1, 2},U = {3}

第三次迭代:

S = {0, 1, 2},U = {3}
需要找S(0,1,2)和U(3)之間的所有邊:
- A-D(無直接邊)
- B-D(1-3): 權值4
- C-D(2-3): 權值5
最小邊是B-D(權值4)。將D(3)加入S:
S = {0, 1, 2, 3},所有頂點已選入。

最終最小生成樹

選中的邊爲:A-B(2)、B-C(1)、B-D(4),總權值=2+1+4=7。

五、算法實現與細節

1. 數據結構表示

圖可以用鄰接矩陣鄰接表表示。對於初學者,鄰接矩陣更直觀(二維數組),例如:

graph = [
    [0, 2, 3, 0],   # A的鄰接:A-B=2,A-C=3
    [2, 0, 1, 4],   # B的鄰接:B-A=2,B-C=1,B-D=4
    [3, 1, 0, 5],   # C的鄰接:C-A=3,C-B=1,C-D=5
    [0, 4, 5, 0]    # D的鄰接:D-B=4,D-C=5
]

其中graph[i][j]表示頂點i和j之間的邊權值(0表示無邊)。

2. 核心步驟僞代碼

// Prim算法僞代碼
function Prim(graph, n):
    // n爲頂點數graph爲鄰接矩陣
    key = [INF] * n   // key數組記錄未選頂點到已選集合的最小邊權
    parent = [-1] * n  // parent數組記錄生成樹的父節點
    key[0] = 0         // 從頂點0開始起點
    selected = [False] * n  // 標記頂點是否已選入S

    for i from 0 to n-1:
        // 找到未選頂點中key最小的u
        u = 找到key最小且selected[u]=False的頂點
        selected[u] = True  // 將u加入已選集合

        // 更新未選頂點的key值
        for v from 0 to n-1:
            if graph[u][v] > 0 and not selected[v] and graph[u][v] < key[v]:
                key[v] = graph[u][v]
                parent[v] = u

    // 計算總權值
    total_weight = sum(key[1..n-1])  // 起點key爲0其他爲加入的邊權
    return total_weight, parent

3. 時間複雜度

  • 使用鄰接矩陣時,外層循環n次,內層循環n次,總複雜度爲O(n²)(n爲頂點數),適合頂點數較少的稠密圖。
  • 若用鄰接表+優先隊列優化,複雜度可降至O(m log n)(m爲邊數),適合稀疏圖。

六、Prim算法的關鍵思想

  • 貪心選擇:每次選擇“連接已選集合和未選集合的最小邊”,確保局部最優。
  • 安全邊性質:無論從哪個頂點開始,最終得到的生成樹總權值一定最小(證明需數學歸納法,初學者可理解爲貪心的合理性)。

七、應用場景

  • 網絡佈線:城市間鋪設光纖,使總費用最低。
  • 電路設計:用最少導線連接所有電子元件,成本最低。
  • 交通規劃:規劃道路,使總長度最短且覆蓋所有區域。

總結

最小生成樹是貪心算法的經典應用,Prim算法通過“從起點逐步擴展,每次選最小邊”的策略,高效構建出總權值最小的生成樹。理解其核心思想(貪心選擇)和步驟,對解決圖論問題(如最短路徑、網絡優化)至關重要。對於初學者,關鍵是通過具體例子(如本文中的4頂點圖)熟悉算法流程,逐步掌握其實現細節。

小夜