力扣 leetcode 1584. 連線所有點的最小費用 (python)並查集加Kruskal演算法
技術標籤:pythonleetcode演算法pythonleetcode資料結構kruskal
Topic
給你一個points 陣列,表示 2D 平面上的一些點,其中 points[i] = [xi, yi] 。
連線點 [xi, yi] 和點 [xj, yj] 的費用為它們之間的 曼哈頓距離 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的絕對值。
請你返回將所有點連線的最小總費用。只有任意兩點之間 有且僅有 一條簡單路徑時,才認為所有點都已連線。
Example_1
輸入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]輸出:20
解釋:
我們可以按照上圖所示連線所有點得到最小總費用,總費用為 20 。
注意到任意兩個點之間只有唯一一條路徑互相到達。
Example_2
輸入:points = [[3,12],[-2,5],[-4,1]]
輸出:18
Example_3
輸入:points = [[0,0],[1,1],[1,0],[-1,1]]
輸出:4
Example_4
輸入:points = [[-1000000,-1000000],[1000000,1000000]]
輸出:4000000
Example_5
輸入:points = [[0,0]]
輸出:0
Solution:
熟悉並查集和Kruskal演算法後很簡單就可以看出這是一道典型的Kruskal演算法題
首先要構造權重邊計算權重:
因為Kruskal演算法以邊為維度
因此需要首先構造所有點兩兩相連時每條邊的權重(這裡是邊的長度)
同時需要將points中原來的邊和權重放在一起放入edges中
之後需要按照權重進行排序
最後通過並查集計算
先例項化並查集並在並查集中構造節點
然後使用並查集構造連通分量
若兩個節點已經連線了,跳過
若兩個節點未連線
將兩個點連線起來
將花費加入結果中
最後返回res即是結果
Code_1
class UnionFind:
def __init__(self):
self.father = {}
def find(self, x):
root = x
while self.father[root] != None:
root = self.father[root]
# 路徑壓縮
while x != root:
original_father = self.father[x]
self.father[x] = root
x = original_father
return root
def merge(self, x, y):
root_x,root_y = self.find(x),self.find(y)
if root_x != root_y:
self.father[root_x] = root_y
def is_connected(self, x, y):
return self.find(x) == self.find(y)
def add(self, x):
if x not in self.father:
self.father[x] = None
class Solution:
def minCostConnectPoints(self, points: List[List[int]]) -> int:
if not points:
return 0
n = len(points)
# 根據Kruskal演算法三部曲:
# 1、構造權重邊:因為Kruskal演算法以邊為維度,因此需要首先構造所有點兩兩相連時每條邊的權重,在這裡就是邊的長度。
edges = []
# 這裡需要兩重迴圈,但是需要注意,第二層迴圈需要以第一層迴圈為起點(兩兩相連嘛)
for node1 in range(n):
for node2 in range(node1, n):
p1 = points[node1]
p2 = points[node2]
# 這裡需要計算權重,也就是邊的長度
edge = (node1, node2, abs(p1[0 ] - p2[0]) + abs(p1[1] - p2[1]))
edges.append(edge)
# 2、排序:對所有的邊按照權重從小到大排序(題目要求是最小的花費)
edges.sort(key=lambda item: item[-1])
# 3、生成最小生成樹:Kruskal是以並查集演算法為基礎的,因此這裡就是通過並查集來構造聯通分量。
uf = UnionFind()
res = 0
# 在並查集中構造節點
for i in range(n):
uf.add(i)
# 使用並查集構造聯通分量
for node1, node2, dest in edges:
if uf.is_connected(node1, node2):
# 如果兩個節點已經連線了,跳過
continue
# 將花費加入結果中
res += dest
# 將兩個點連線起來
uf.merge(node1, node2)
return res
Result_1:
optimize
程式碼中僅使用了路徑壓縮
我們同樣可以使用按秩合併(並查集中size的優化)進行優化
size優化我之前總結了模板可供參考
Code_2
class UnionFind:
def __init__(self, n):
self.father = {}
self.size = [0] * n
def find(self, x):
root = x
while self.father[root] != None:
root = self.father[root]
# 路徑壓縮
while x != root:
original_father = self.father[x]
self.father[x] = root
x = original_father
return root
def merge(self, x, y):
root_x,root_y = self.find(x),self.find(y)
if root_x == root_y:
return False
if self.size[root_x] < self.size[root_y]:
root_x, root_y = root_y, root_x
self.size[root_x] += self.size[root_y]
self.father[root_x] = root_y
def is_connected(self, x, y):
return self.find(x) == self.find(y)
def add(self, x):
if x not in self.father:
self.father[x] = None
class Solution:
def minCostConnectPoints(self, points: List[List[int]]) -> int:
if not points:
return 0
n = len(points)
# 根據Kruskal演算法三部曲:
# 1、構造權重邊:因為Kruskal演算法以邊為維度,因此需要首先構造所有點兩兩相連時每條邊的權重,在這裡就是邊的長度。
edges = []
# 這裡需要兩重迴圈,但是需要注意,第二層迴圈需要以第一層迴圈為起點(兩兩相連嘛)
for node1 in range(n):
for node2 in range(node1, n):
p1 = points[node1]
p2 = points[node2]
# 這裡需要計算權重,也就是邊的長度
edge = (node1, node2, abs(p1[0 ] - p2[0]) + abs(p1[1] - p2[1]))
edges.append(edge)
# 2、排序:對所有的邊按照權重從小到大排序(題目要求是最小的花費)
edges.sort(key=lambda item: item[-1])
# 3、生成最小生成樹:Kruskal是以並查集演算法為基礎的,因此這裡就是通過並查集來構造聯通分量。
uf = UnionFind(n)
res = 0
# 在並查集中構造節點
for i in range(n):
uf.add(i)
# 使用並查集構造聯通分量
for node1, node2, dest in edges:
if uf.is_connected(node1, node2):
# 如果兩個節點已經連線了,跳過
continue
# 將花費加入結果中
res += dest
# 將兩個點連線起來
uf.merge(node1, node2)
return res
Result_2:
即使leetcode執行時間誤差較大
但由於兩者取得都是近乎最佳執行效率的情況
可以看出還是有一定執行效率的提高的