LeetCode刷题(每日一题) --1584. 连接所有点的最小费用(并查集)

题目

给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。

连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。

请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

示例 1:
在这里插入图片描述

输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]] 输出:20 解释:
在这里插入图片描述
我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。 注意到任意两个点之间只有唯一一条路径互相到达。

示例 2:

输入:points = [[3,12],[-2,5],[-4,1]] 输出:18

示例 3:

输入:points = [[0,0],[1,1],[1,0],[-1,1]] 输出:4

示例 4:

输入:points = [[-1000000,-1000000],[1000000,1000000]] 输出:4000000

示例 5:

输入:points = [[0,0]] 输出:0

提示:

1 <= points.length <= 1000
-106 <= xi, yi <= 106
所有点 (xi, yi) 两两不同。

解答

连接所有点的最小费用,即最小生成树。

Prim解法

思路

两个假象集合(并不需要实际构造,理论上的成立):V(未合并的点集)Vnew(合并的点集)

  • 所有的节点都在集合V中
  • 每次取出一个最小路径的节点加入Vnew中(结果)

构造的数据结构

  • lowcost:表示Vnew中的点的集合(总)到V中某个节点(单个)的最小距离。长度等于节点总数,已经加入Vnew的节点的变量设置为-1,其余的则设置为当前的距离。
  • v数组,表示对V的访问情况,长度为节点总数,未加入的为0,加入的为-1

步骤

  1. 随机一个起点,并将其加入到Vnew中,更新lowcost和v
  2. 遍历lowcast,寻找当前的最小距离,以及其索引。根据索引将其加入到Vnew中,也就是在v中其值设置为-1,表示已经访问。
  3. 同时根据索引更新lowcast,加入一个点之后,所有其他的点的距离都可能缩短
  4. 重复步骤2,直到访问所有节点

结果

最后需要返回的就是每次需找到的lowcast的最小和,因此,只要提前设置一个变量,就可以获得其结果。
在这里插入图片描述
在这里插入图片描述

    //prim
class Solution {
public:
    int Prim(vector<vector<int>> &points,int start)
    {
        unsigned INI_MAX = -1;
        int n=points.size();
        int res = 0;
        //1.将points转化成临界矩阵
        vector<vector<int>> g(n,vector<int>(n));//创建二维动态数组
        for(int i=0;i<n;++i)
        {
            //计算的是当前点与其之后的所有点的距离,构成一个上三角,因此必须同时进行两次赋值,这样才能构成完整的矩阵
            for(int j=i+1;j<n;++j)
            {
                int dist = abs(points[i][0]-points[j][0])+abs(points[i][1]-points[j][1]);
                g[i][j] = dist;
                g[j][i] = dist;
            }
        }
        //记录V[i]到Vnew的最近距离
        vector<int> lowcast(n,INI_MAX);
        //记录V[i]是否加入到了Vnew
        vector<int> v(n,-1);

        //2.先将start加入得到Vnew
        v[start] = 0;
        for(int i=0;i<n;++i)
        {
            if(i==start) continue;
            lowcast[i] = g[i][start];
        }

        //3.剩余n-1个节点未加入到Vnew,遍历
        for(int i=1;i<n;++i)
        {
            //找出此时V中,离Vnew最近的点
            int minIdx = -1;
            unsigned minVal = INI_MAX;
            for(int j=0;j<n;++j)
            {
                if(v[j]==0) continue;
                if(lowcast[j]<minVal)
                {
                    minIdx = j;
                    minVal = lowcast[j];
                }
            }
            //跟新当前最小和,该点的标记,该点的距离
            res += minVal;
            v[minIdx] = 0;
            lowcast[minIdx] = -1;

            //跟新lowcast中的最小距离
            for(int j=0;j<n;++j)
            {
                if(v[j]==-1&&g[j][minIdx]<lowcast[j])
                    lowcast[j] = g[j][minIdx];
            }
        }
        return res;
    }
    int minCostConnectPoints(vector<vector<int>>& points) {
        return Prim(points,0);
    }
};

Kruskal(并查集)

思路

Kruskal与prim的不同:

  • Prim算法是以顶点为基础(每次寻找离Vnew最近的顶点)
  • Kruskal算法是以边为基础,每次从边中寻找最小的边(不管两个顶点是属于V还是Vnew)之后通过顶点判断是否属于同一个连通图
  • kruskal需要对所有边进行排序,然后从小到大,依次遍历每条边,并且判断其连通性,直到所有顶点都属于同一个连通图

数据结构

  • 并查集
  • 点-边式
struct VP
{
	int start;
	int end;
	int length;
}

步骤

  1. 初始化,将图转化成点边式,并对其结构进行排序,同时初始化并查集
  2. 依次遍历所有点-边式,取最小值
  3. 做如下判断:如果当前选择的最小的边的两个顶点是属于同一个连通图,跳过;否则,将两个顶点进行合并
  4. 重复步骤2,直到存在一个连通量。包含了所有的节点

结果

在这里插入图片描述
在这里插入图片描述

//Kruskal(并查集)
class Kruskal
{
public:
    vector<int> parent; //记录节点的根
    vector<int> rank; //记录根节点的深度(秩优化)
    vector<int> size; //每个连通分量的节点个数
    vector<int> len; //记录每个连通分量的所有边长度
    int num; //记录节点个数
    Kruskal(int n):parent(vector<int>(n)),rank(vector<int>(n)),len(n,0),size(n,1),num(n)
    {
        for(int i=0;i<n;++i)
            parent[i] = i;
    }

    int Find(int index)
    {
        if(index!=parent[index]) parent[index] = Find(parent[index]);
        return parent[index];
    }

    int Union(int index1,int index2,int length)
    {
        int find_1 = Find(index1);
        int find_2 = Find(index2);
        if(find_1!=find_2)
        {
            //让秩小的去连通秩大的,避免增加连接后的长度
            if(rank[find_1]>rank[find_2])
            {
                swap(find_1,find_2);
            }
            parent[find_1] = find_2;
            //修改秩
            if(rank[find_1]==rank[find_2]) ++rank[find_2];//秩相同的话,增加一长度
            //修改合并之后的节点个数,边长度
            size[find_2] += size[find_1];
            len[find_2] += len[find_1]+length;
            //如果某个连通分量的节点数包括了所有节点,直接返回该分量的所有边长度
            if(size[find_2]==num) return len[find_2];
        }
        return -1;
    }
};

 //点-边结构体
struct VP
{
    int start;
    int end;
    int len;
};

class Solution
{
public:
    int minCostConnectPoints(vector<vector<int>>& points)
    {
        int res = 0;
        int n = points.size();
        Kruskal ds(n);
        vector<VP> edges;
        //建立点-边数据结构
        for(int i=0;i<n;++i)
        {
            for(int j=i+1;j<n;++j)
            {
                VP vp = {i,j,abs(points[i][0]-points[j][0])+abs(points[i][1]-points[j][1])};
                edges.emplace_back(vp);
            }
        }
        //按边长排序
        sort(edges.begin(),edges.end(),[](const auto& a,const auto& b)
        {
            return a.len<b.len;
        });

        //连通分量合并
        for(auto&e:edges)
        {
            res = ds.Union(e.start, e.end, e.len);
            if(res!=-1) return res;
        }
        return 0;
    }
};

总结

prim的复杂度

  • 时间复杂度:O(n * n)

  • 空间复杂度:O(n * n)

Kruskal复杂度

  • 时间复杂度:O(m log(m) + m α(m) )
  • 空间复杂度:O(n * n)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hanzoe_lwh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值