1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
| #include <iostream> #include <vector> #include <queue> #include <functional>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int prim(int n, const vector<vector<pair<int, int>>>& adj) { int total_weight = 0; int count = 0; vector<int> dist(n + 1, INF); vector<bool> vis(n + 1, false); priority_queue<PII, vector<PII>, greater<PII>> pq;
dist[1] = 0; pq.push({0, 1});
while (!pq.empty()) { auto [d, u] = pq.top(); pq.pop();
if (vis[u]) continue;
vis[u] = true; total_weight += d; count++;
for (auto& edge : adj[u]) { int v = edge.first; int w = edge.second;
if (!vis[v] && w < dist[v]) { dist[v] = w; pq.push({w, v}); } } }
if (count < n) return -1; return total_weight; }
int main() { int n, m; if (cin >> n >> m) { vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; adj[u].push_back({v, w}); adj[v].push_back({u, w}); }
int result = prim(n, adj);
if (result == -1) { cout << "The graph is disconnected, MST does not exist." << endl; } else { cout << "Total Weight of MST: " << result << endl; } } return 0; } ````
---
## 4. 关键点深度解析
### 4.1 为什么要用 `visited` 数组?
在优先队列中,同一个节点可能会被多次压入。
例如:点 $C$ 离 $MST$ 的距离原先是 10(通过点 A),后来发现通过点 B 距离只有 5。我们会把 {5, C} 压入堆,但旧的 {10, C} 还在堆里。
当 {5, C} 弹出并处理完后,vis[C] 变为 true。等轮到 {10, C} 弹出时,通过 if (vis[u]) continue; 这一行代码可以直接忽略它。这叫懒惰删除 (Lazy Deletion)。
### 4.2 `dist` 数组的含义
在 Dijkstra 算法中,dist[i] 代表“从起点到 $i$ 的最短路径长度”。
但在 Prim 算法中,dist[i] 代表 “节点 $i$ 到当前生成的这棵树(集合 $S$)的最短距离”。这是两者最大的区别。
### 4.3 为什么适合邻接表?
- **普通 Prim (邻接矩阵)**:每次寻找最近点需要扫描 `dist` 数组,复杂度 $O(V)$,总复杂度 $O(V^2)$。 - **堆优化 Prim (邻接表)**: - 寻找最近点:$O(\log E)$ (从堆弹出的开销)。 - 更新邻居:每条边扫描一次,堆操作一次。 - **总时间复杂度**:$O(E \log V)$ 或 $O(E \log E)$。 - 在稀疏图($E \ll V^2$)中,这种写法远快于邻接矩阵版。
---
## 5. 复杂度分析
| **版本** | **存储结构** | **查找最小值方式** | **时间复杂度** | **适用场景** | | ------------ | -------- | -------------- | ----------------- | -------------- | | **朴素 Prim** | 邻接矩阵 | 线性扫描数组 | $O(V^2)$ | 稠密图 (点少边多) | | **堆优化 Prim** | **邻接表** | **优先队列 (二叉堆)** | **$O(E \log V)$** | **稀疏图 (点多边少)** |
由于大多数工程应用和算法题目给出的图都是稀疏图(例如城市路网),使用**邻接表 + 优先队列**是更加通用的选择。
# Prim 算法 vs Dijkstra 算法逻辑对比
### 1. 核心公式区别 - **Dijkstra (最短路):** 寻找路径总和最小。 $$ dist[v] = \min(dist[v], dist[u] + weight(u, v)) $$ - **Prim (最小生成树):** 寻找连接节点的单边最小。 $$ dist[v] = \min(dist[v], weight(u, v)) $$
### 2. 修正后的 Prim 代码片段 ```cpp void prim(vector<vector<pair<int,int>>> &adj, int n, int start, vector<int> &parent) { vector<int> dist(n + 1, INF); vector<bool> vis(n + 1, false); priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
dist[start] = 0; pq.push({0, start});
while (!pq.empty()) { int u = pq.top().second; pq.pop();
if (vis[u]) continue; vis[u] = true;
for (auto &edge : adj[u]) { int v = edge.first; int w = edge.second; if (!vis[v] && w < dist[v]) { dist[v] = w; parent[v] = u; pq.push({dist[v], v}); } } } }
|