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
解析
每个point是一个vertex,两个point之间的曼哈顿距离是cost,转化为图的最小生成树问题。
代码prim最小生成树
class Solution {
int prim(vector<vector<int>>& graph,int n,int src){
vector<bool> vis(n+1,false);//是否已加入最小生成树
vis[src]=true;
vector<int> dis(n+1,INT_MAX);//当前最小树到第i个节点的距离
for(int i=1;i<=n;i++){
dis[i]=graph[src][i];
}
//计算最小生成树
for(int i=1;i<=n;i++){
//1.寻找尚未搜索的集合中,最小代价的节点
int minCost=INT_MAX;
int v=1;
for(int i=1;i<=n;i++){
if(!vis[i]&&dis[i]<minCost){
minCost=dis[i];
v=i;
}
}
//2.
if(dis[v]==INT_MAX)break;
//3.访问v节点,加入最小生成树,并更新v的相邻节点
vis[v]=true;
for(int i=1;i<=n;i++){
if(!vis[i]&&graph[v][i]<dis[i]){
dis[i]=graph[v][i];
}
}
}
int cost=0;//总代价
for(int i=1;i<=n;i++){
if(dis[i]==INT_MAX)return -1;//不存在最小生成树
cost+=dis[i];
}
return cost;
}
public:
int minCostConnectPoints(vector<vector<int>>& points) {
int n=points.size();
vector<vector<int>> graph(n+1,vector<int>(n+1));
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
graph[i][j]=graph[j][i]=abs(points[i-1][0]-points[j-1][0])+abs(points[i-1][1]-points[j-1][1]);
}
}
int ans=prim(graph,n,1);//总距离
return ans;
}
};
kruskal最小生成树
从小到大加入边,是一个贪心算法。
其算法流程为:
- 将图 G={V,E} 中的所有边按照长度由小到大进行排序,等长的边可以按任意顺序。
- 初始化图 G’{V,∅},从前向后扫描排序后的边,如果扫描到的边 e 在 G’ 中连接了两个相异的连通块,则将它插入 G’ 中。
- 最后得到的图 G′ 就是图 G 的最小生成树。
class DisjointSetUnion{
private:
vector<int> parent;
vector<int> rank;
int n;
public:
DisjointSetUnion(int n_){
n=n_;
rank.resize(n,1);
parent.resize(n);
for(int i=0;i<n;i++){
parent[i]=i;
rank[i]=0;
}
}
int Find(int x){
if(parent[x]!=x)
parent[x]=Find(parent[x]);
return parent[x];
}
void Union(int x,int y){
int xRoot=Find(x);
int yRoot=Find(y);
if(xRoot==yRoot)
return;
//rank高的作为父节点
if(rank[xRoot]>=rank[yRoot])
parent[yRoot]=xRoot;
else{
parent[xRoot]=yRoot;
}
}
};
struct Edge {
int len, from, to;
Edge(int len, int from, int to) : len(len), from(from), to(to) {
}
};
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points) {
int n = points.size();
vector<Edge> edges;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
int x = points[i][0] - points[j][0];
int y = points[i][1] - points[j][1];
edges.push_back(Edge(abs(x) + abs(y), i, j));
}
}
sort(edges.begin(), edges.end(),[](const Edge& a, const Edge& b) {
return a.len < b.len;
});
DisjointSetUnion dsu(n);
int ans = 0;
for (auto& e : edges) {
if (dsu.Find(e.from) != dsu.Find(e.to)) {
ans += e.len;
dsu.Union(e.from, e.to);
}
}
return ans;
}
};
复杂度
prim:
T(n)=O(n2)
kruskal时间复杂度:O(n2log(n)),其中 n是节点数。一般Kruskal 是 O(mlogm) 的算法,m是边数,但本题中 m=n2 ,因此总时间复杂度为O(n2log(n))
空间复杂度:O(n2),其中 n是节点数。并查集使用 O(n)的空间,边集数组需要使用 O(n2 ) 的空间。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)