Spanning Tree

本文介绍了两种寻找无向图中最小生成树(MST)的方法:Kruskal算法和Prim算法,并详细解释了这两种算法的实现过程及其时间复杂度。

转自:http://www.csie.ntnu.edu.tw/~u91029/SpanningTree.html

先收藏了,以后好好看一下。

Spanning Tree / Spanning Forest

中譯「生成樹」,從一張圖上分離出一棵包含圖上所有點的樹,便是這張圖的生成樹。一張圖的生成樹可能會有很多種。

當一張圖完全連通,必然有生成樹。當一張圖有部份不連通,則沒有生成樹,而是許多棵「生成子樹」所構成的「生成森林」。宛如 DFS tree 與 DFS forest 的關係。

生成樹也可以有權重。當圖上每條邊都有權重時,生成樹的權重為樹上每條邊的權重總和。

Minimum Spanning Tree

中譯「最小生成樹」。權重最小的生成樹就是最小生成樹。一張圖的最小生成樹可能會有很多種。

Minimum Spanning Tree: 
Kruskal's Algorithm

程度★ 難度★★

用途

求出無向圖的其中一棵最小(大)生成樹。若是圖不連通,則是求出其中一叢最小(大)生成森林。

演算法

一、兩棵 MST ,要合併成一棵 MST 時,以兩棵 MST 之間權重最小的邊進行連結,當然會是最好的。

二、三棵 MST ,要合併成一棵 MST 時,先連結其中兩棵連結權重最小的 MST ,然後才連結第三棵,總是比較好。

三、一個單獨的點,可以視作一棵 MST 。

由以上三點,可以歸納出一個 greedy 演算法:以權重最小的邊連結各棵 MST ,一定比較好。

一、一開始圖上每一個點,各自是一棵最小生成子樹MSS。
二、圖上所有邊,依照權重大小,由小到大排序。
三、依序嘗試圖上所有邊,作為最小生成樹(森林)上的邊:
 甲、兩端點分別位於兩棵MSS,也就是產生了橋:
   用這條邊連接兩棵MSS,合併成一棵MSS。
   這條邊會是最小生成樹(森林)上的邊。
 乙、兩端點皆位於同一棵MSS,也就是產生了環:
   捨棄這條邊。

每次選中的邊,都是 MST 上的邊。沒有選中的邊,不論這張圖以後又增加了多少邊,絕不會成為 MST 上的邊。

時間複雜度

一、排序圖上所有邊,需時 O(ElogE) 。

二、連接 MSS ,一般是運用「 Disjoint-set Forest 」,需時 O(E*α(E,V)) 。

故時間複雜度為 O(ElogE) 。

 
  1. const int V = 100E = 1000;
  2.  
  3. struct Edge {int abc;} e[E];    // edge list
  4. bool operator<(const Edgee1const Edgee2) {return e1.c < e2.c;}
  5.  
  6. // disjoint-sets forest
  7. int p[V];
  8. int init() {for (int i=0i<V; ++ip[i] = i;}
  9. int find(int x) {return x == p[x] ? x : (p[x] = find(p[x]));}
  10. void union(int xint y) {p[find(x)] = find(y);}
  11.  
  12. void Kruskal()
  13. {
  14.     init();
  15.  
  16.     // 圖上所有邊,依照權重大小,由小到大排序。
  17.     sort(edgeedge+E); // O(NlogN)
  18.  
  19.     // 依序找出最小生成樹上的V-1條邊。
  20.     int ij;
  21.     for (i = 0j = 0i < V-1 && j < E; ++i)
  22.     {
  23.         // 產生環,則捨棄。直到產生橋。
  24.         while (find(e[j].a) == find(e[j].b)) j++;
  25.  
  26.         // 產生橋,則以此邊連接兩棵MSS。
  27.         union(e[j].ae[j].b);
  28.  
  29.         // 印出最小生成樹(森林)上的邊。
  30.         cout << "起點:" << e[j].a
  31.             << "終點:" << e[j].b
  32.             << "權重:" << e[j].c;
  33.  
  34.         j++;    // 別忘記累計索引值。也可以寫入迴圈。
  35.     }
  36.  
  37.     if (i == Vcout << "得到最小生成樹";
  38.     else        cout << "得到最小生成森林";
  39. }

迴圈的部份也可以寫成這樣。

 
  1.     // 窮舉圖上所有邊,嘗試作為最小生成樹(森林)。
  2.     for (i = 0j = 0i < V-1 && j < E; ++j)
  3.     {
  4.         // 產生環,則捨棄。
  5.         if (find(e[j].a) == find(e[j].b)) continue;
  6.  
  7.         // 產生橋,則以此邊連接兩棵MSS。
  8.         union(e[j].ae[j].b);
  9.  
  10.         // 印出最小生成樹(森林)上的邊。
  11.         cout << "起點:" << e[j].a
  12.             << "終點:" << e[j].b
  13.             << "權重:" << e[j].c;
  14.  
  15.         i++;    // 別忘記累計索引值。不可以寫入迴圈。
  16.     }

UVa 908 10369

Minimum Spanning Tree: 
Prim's Algorithm

程度★ 難度★★

用途

求出無向圖的其中一棵最小(大)生成樹。

演算法

與「 Shortest Path: Dijkstra's Algorithm 」的概念大致相同。

主要的差異是: Dijkstra's Algorithm 屢次找不在樹上、離根最近的點, Prim's Algorithm 屢次找不在樹上、離樹最近的點。

另外一個差異是:最短路徑樹擁有特定起點,而最小生成樹可以選定任何一點作為樹根。

時間複雜度

圖的資料結構為 adjacency matrix 的話,便是 O(V^2) ;圖的資料結構為 adjacency lists 的話,還是 O(V^2) 。

就和 Dijkstra's Algorithm 一樣, Prim's Algorithm 也可以使用 Fibonacci Heap 、 Priority Queue ,得到更低的時間複雜度。

 
  1. int w[9][9];    // adjacency matrix
  2. int d[9];       // 紀錄目前的MST到圖上各點的距離
  3. int parent[9];  // 紀錄各個點在MST上的父親是誰
  4. bool visit[9];  // 紀錄各個點是不是已在MST之中
  5.  
  6. void prim()
  7. {
  8.     for (int i=0i<9i++) visit[i] = false;
  9.     for (int i=0i<9i++) d[i] = 1e9;
  10.  
  11.     d[0] = 0;   // 可以選定任何點作為樹根,這裡以第零點作為樹根。
  12.     parent[0] = 0;
  13.  
  14.     for (int i=0i<9i++)
  15.     {
  16.         int a = -1b = -1min = 1e9;
  17.         for (int j=0j<9j++)
  18.             if (!visit[j] && d[j] < min)
  19.             {
  20.                 a = j;  // 記錄這一條邊
  21.                 min = d[j];
  22.             }
  23.  
  24.         if (a == -1break// 與起點相連通的MST都已找完
  25.         visit[a] = true;
  26. //      d[a] = 0;           // 註解後,得到MST每條邊權重。
  27.  
  28.         for (b=0b<9b++)
  29.             // 以下與Dijkstra's Algorithm略有不同
  30.             if  (!visit[b] && w[a][b] < d[b])
  31.             {
  32.                 d[b] = w[a][b]; // 離樹最近,不是離根最近。
  33.                 parent[b] = a;
  34.             }
  35.     }
  36. }

UVa 10034 10147 10307 10397 10600 10842

© 2010 tkcn . All rights reserved.

1
10
16
16
16
2
11
13
11
2
13
0
1
2
3
4
5
6
7
8
9
節點編號 0 1 2 3 4 5 6 7 8 9
距離樹的距離 0


### 生成树算法及其在网络中的应用 生成树(Spanning Tree)是图论中的一个核心概念,主要用于解决连通图中的最简子图问题。生成树是一个无环的连通子图,它包含图中的所有顶点,并且尽可能少地使用边。最小生成树(Minimum Spanning Tree, MST)则是所有生成树中边的权重总和最小的一种,常用于优化网络设计。 #### 生成树算法 生成树算法主要分为两种:**Prim算法**和**Kruskal算法**。两者均属于贪心算法,用于寻找加权图中的最小生成树。 - **Prim算法**:该算法从一个顶点开始,逐步添加边以连接其他顶点。每一步都选择当前生成树与其他顶点之间权重最小的边。Prim算法的时间复杂度为 $O(V^2)$,若使用堆优化可降至 $O(E \log V)$。[^1] - **Kruskal算法**:该算法首先将所有边按权重从小到大排序,然后依次选择边并检查是否形成环。如果不会形成环,则将该边加入生成树。Kruskal算法的时间复杂度主要由排序决定,为 $O(E \log E)$。[^1] #### 网络中的应用 生成树算法在现实世界中有广泛的应用,尤其是在网络设计领域。 - **计算机网络**:在局域网(LAN)中,生成树协议(Spanning Tree Protocol, STP)用于防止网络中的环路问题。通过构建生成树,确保数据包在网络中不会无限循环,同时保持网络的连通性。[^1] - **交通规划**:在道路网络设计中,生成树算法可用于优化交通路线,确保所有城市之间的连通性,并最小化建设成本。例如,在海平面上升后,部分城市需要重新建设道路,通过Prim算法可以计算出连接所有城市的最低成本方案。[^2] - **电力网络**:在电力传输网络中,最小生成树用于优化电缆铺设路径,以最小的成本实现所有节点的连通。例如,通过构建发电站为初始点的最小生成树,可以计算出电缆铺设的最短总长度,从而减少建设成本。[^3] #### 示例代码:Prim算法实现 以下是一个使用邻接矩阵实现Prim算法的Python代码示例: ```python import sys def prim(graph, num_vertices): # 初始化数组 key = [sys.maxsize] * num_vertices mst_set = [False] * num_vertices parent = [None] * num_vertices key[0] = 0 parent[0] = -1 # 第一个顶点作为根节点 for _ in range(num_vertices): # 找到当前最小键值的顶点 u = min_key(key, mst_set) mst_set[u] = True # 更新相邻顶点的键值 for v in range(num_vertices): if graph[u][v] > 0 and mst_set[v] == False and key[v] > graph[u][v]: key[v] = graph[u][v] parent[v] = u # 输出生成树的总权重 total_cost = sum(key) print("最小生成树的总花费为:", total_cost) def min_key(key, mst_set): min_index = -1 min_value = sys.maxsize for v in range(len(key)): if mst_set[v] == False and key[v] < min_value: min_value = key[v] min_index = v return min_index # 示例图的邻接矩阵 graph = [ [0, 2, 0, 6, 0], [2, 0, 3, 8, 5], [0, 3, 0, 0, 7], [6, 8, 0, 0, 9], [0, 5, 7, 9, 0] ] num_vertices = 5 prim(graph, num_vertices) ``` #### 示例代码:Kruskal算法实现 以下是一个使用并查集(Union-Find)实现Kruskal算法的Python代码示例: ```python class UnionFind: def __init__(self, vertices): self.parent = list(range(vertices)) def find(self, u): if self.parent[u] != u: self.parent[u] = self.find(self.parent[u]) return self.parent[u] def union(self, u, v): root_u = self.find(u) root_v = self.find(v) if root_u != root_v: self.parent[root_v] = root_u def kruskal(graph, num_vertices): # 将边按照权重排序 edges = [] for u in range(num_vertices): for v in range(num_vertices): if graph[u][v] > 0 and u < v: edges.append((graph[u][v], u, v)) edges.sort() uf = UnionFind(num_vertices) mst = [] total_cost = 0 for edge in edges: weight, u, v = edge if uf.find(u) != uf.find(v): uf.union(u, v) mst.append((u, v, weight)) total_cost += weight print("最小生成树的总花费为:", total_cost) # 示例图的邻接矩阵 graph = [ [0, 2, 0, 6, 0], [2, 0, 3, 8, 5], [0, 3, 0, 0, 7], [6, 8, 0, 0, 9], [0, 5, 7, 9, 0] ] num_vertices = 5 kruskal(graph, num_vertices) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值