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
步骤
- 随机一个起点,并将其加入到Vnew中,更新lowcost和v
- 遍历lowcast,寻找当前的最小距离,以及其索引。根据索引将其加入到Vnew中,也就是在v中其值设置为-1,表示已经访问。
- 同时根据索引更新lowcast,加入一个点之后,所有其他的点的距离都可能缩短
- 重复步骤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;
}
步骤
- 初始化,将图转化成点边式,并对其结构进行排序,同时初始化并查集
- 依次遍历所有点-边式,取最小值
- 做如下判断:如果当前选择的最小的边的两个顶点是属于同一个连通图,跳过;否则,将两个顶点进行合并
- 重复步骤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)
标签:vector,--,查集,1584,节点,Vnew,int,points,find 来源: https://blog.csdn.net/weixin_43216249/article/details/112821632