力扣 leetcode 1584. 连接所有点的最小费用 (python)并查集加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算法

首先要构造权重边计算权重:
因为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运行时间误差较大
但由于两者取得都是近乎最佳运行效率的情况
可以看出还是有一定运行效率的提高的

在这里插入图片描述

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页