Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9615377

Browse files
committed
补充「图的最小生成树」相关内容
1 parent fe267b2 commit 9615377

File tree

1 file changed

+137
-7
lines changed

1 file changed

+137
-7
lines changed

‎Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
在了解「最小生成树」之前,我们需要要先理解 「生成树」的概念。
44

5-
> **图的生成树(Spanning Tree)**:如果无向连通图 G 的一个子图是一棵包含图 G 所有顶点的树,则称该子图为 G 的生成树。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。
5+
> **生成树(Spanning Tree)**:如果无向连通图 G 的一个子图是一棵包含图 G 所有顶点的树,则称该子图为 G 的生成树。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。
66
77
换句话说,生成树是原图 G 的一个子图,它包含了原图 G 的所有顶点,并且通过选择图中一部分边连接这些顶点,使得子图中没有环。
88

@@ -51,12 +51,73 @@
5151
### 2.3 Prim 算法的实现代码
5252

5353
```python
54-
54+
class Solution:
55+
# graph 为图的邻接矩阵,start 为起始顶点
56+
def Prim(self, graph, start):
57+
size = len(graph)
58+
vis = set()
59+
dist = [float('inf') for _ in range(size)]
60+
61+
ans = 0 # 最小生成树的边权和
62+
dist[start] = 0 # 初始化起始顶点到起始顶点的边权值为 0
63+
64+
for i in range(1, size): # 初始化起始顶点到其他顶点的边权值
65+
dist[i] = graph[start][i]
66+
vis.add(start) # 将 start 顶点标记为已访问
67+
68+
for _ in range(size - 1):
69+
min_dis = float('inf')
70+
min_dis_pos = -1
71+
for i in range(size):
72+
if i not in vis and dist[i] < min_dis:
73+
min_dis = dist[i]
74+
min_dis_pos = i
75+
if min_dis_pos == -1: # 没有顶点可以加入 MST,图 G 不连通
76+
return -1
77+
ans += min_dis # 将顶点加入 MST,并将边权值加入到答案中
78+
vis.add(min_dis_pos)
79+
for i in range(size):
80+
if i not in vis and dist[i] > graph[min_dis_pos][i]:
81+
dist[i] = graph[min_dis_pos][i]
82+
return ans
83+
84+
points = [[0,0]]
85+
graph = dict()
86+
size = len(points)
87+
for i in range(size):
88+
x1, y1 = points[i]
89+
for j in range(size):
90+
x2, y2 = points[j]
91+
dist = abs(x2 - x1) + abs(y2 - y1)
92+
if i not in graph:
93+
graph[i] = dict()
94+
if j not in graph:
95+
graph[j] = dict()
96+
graph[i][j] = dist
97+
graph[j][i] = dist
98+
99+
100+
print(Solution().Prim(graph))
55101
```
56102

57-
### 2.3 Prim 算法
103+
### 2.4 Prim 算法复杂度分析
104+
105+
Prim 算法的时间复杂度主要取决于以下几个因素:
106+
107+
1. **初始化阶段**:
108+
- 初始化距离数组和访问数组的时间复杂度为 $O(V),ドル其中 $V$ 是图中的顶点数。
58109

59-
## 03. Kruskal 算法
110+
2. **主循环阶段**:
111+
- 外层循环需要执行 $V-1$ 次,用于选择 $V-1$ 条边。
112+
- 每次循环中需要:
113+
- 找到未访问顶点中距离最小的顶点,时间复杂度为 $O(V)$。
114+
- 更新相邻顶点的距离,时间复杂度为 $O(V)$。
115+
116+
因此,Prim 算法的总体复杂度为:
117+
- 时间复杂度:$O(V^2),ドル其中 $V$ 是图中的顶点数。
118+
- 空间复杂度:$O(V),ドル主要用于存储距离数组和访问数组。
119+
120+
## 3. Kruskal 算法
60121

61122
### 3.1 Kruskal 算法的算法思想
62123

@@ -70,12 +131,81 @@
70131
2. 将每个顶点看做是一个单独集合,即初始时每个顶点自成一个集合。
71132
3. 按照排好序的边顺序,按照权重从小到大,依次遍历每一条边。
72133
4. 对于每条边,检查其连接的两个顶点所属的集合:
73-
1. 如果两个顶点属于同一个集合,则跳过这条边,以免形成环路。
74-
2. 如果两个顶点不属于同一个集合,则将这条边加入到最小生成树中,同时合并这两个顶点所属的集合。
134+
1. 如果两个顶点属于同一个集合,则跳过这条边,以免形成环路。
135+
2. 如果两个顶点不属于同一个集合,则将这条边加入到最小生成树中,同时合并这两个顶点所属的集合。
75136
5. 重复第 3ドル \sim 4$ 步,直到最小生成树中的变数等于所有节点数减 1ドル$ 为止。
76137

77138
### 3.3 Kruskal 算法的实现代码
78139

79140
```python
141+
class UnionFind:
142+
143+
def __init__(self, n):
144+
self.parent = [i for i in range(n)]
145+
self.count = n
146+
147+
def find(self, x):
148+
while x != self.parent[x]:
149+
self.parent[x] = self.parent[self.parent[x]]
150+
x = self.parent[x]
151+
return x
152+
153+
def union(self, x, y):
154+
root_x = self.find(x)
155+
root_y = self.find(y)
156+
if root_x == root_y:
157+
return
158+
159+
self.parent[root_x] = root_y
160+
self.count -= 1
161+
162+
def is_connected(self, x, y):
163+
return self.find(x) == self.find(y)
164+
165+
166+
class Solution:
167+
def Kruskal(self, edges, size):
168+
union_find = UnionFind(size)
169+
170+
edges.sort(key=lambda x: x[2])
171+
172+
res, cnt = 0, 1
173+
for x, y, dist in edges:
174+
if union_find.is_connected(x, y):
175+
continue
176+
ans += dist
177+
cnt += 1
178+
union_find.union(x, y)
179+
if cnt == size - 1:
180+
return ans
181+
return ans
182+
183+
def minCostConnectPoints(self, points: List[List[int]]) -> int:
184+
size = len(points)
185+
edges = []
186+
for i in range(size):
187+
xi, yi = points[i]
188+
for j in range(i + 1, size):
189+
xj, yj = points[j]
190+
dist = abs(xi - xj) + abs(yi - yj)
191+
edges.append([i, j, dist])
192+
193+
ans = Solution().Kruskal(edges, size)
194+
return ans
195+
```
196+
197+
### 3.4 Kruskal 算法复杂度分析
198+
199+
Kruskal 算法的时间复杂度主要取决于以下几个因素:
200+
201+
1. **边的排序**:对 $E$ 条边进行排序的时间复杂度为 $O(E \log E)$。
202+
203+
2. **并查集操作**:
204+
- 查找操作(find)的时间复杂度为 $O(\alpha(n)),ドル其中 $\alpha(n)$ 是阿克曼函数的反函数,增长极其缓慢,可以近似认为是常数时间。
205+
- 合并操作(union)的时间复杂度也是 $O(\alpha(n))$。
206+
207+
3. **遍历边的过程**:需要遍历所有边,时间复杂度为 $O(E)$。
80208

81-
```
209+
因此,Kruskal 算法的总体时间复杂度为:
210+
- 时间复杂度:$O(E \log E),ドル其中 $E$ 是图中的边数。
211+
- 空间复杂度:$O(V),ドル其中 $V$ 是图中的顶点数,主要用于存储并查集数据结构。

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /