给你一个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) 两两不同。
解题思路
如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树。 生成树是连通图的包含图中的所有顶点的极小连通子图。 图的生成树不惟一。 从不同的顶点出发进行遍历,可以得到不同的生成树。
求连接所有点得到最小总费用,即求最小生成树:一副连通加权无向图中一棵权值最小的生成树。
最小生成树的两种经典算法:Prim算法和Kruskal算法
一、Prim算法
算法描述
- 输入:一个加权连通图,其中顶点集合为V,边集合为E;
- 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
- 重复下列操作,直到Vnew = V:
a. 在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一); b. 将v加入集合Vnew中,将<u, v>边加入集合Enew中; - 输出:使用集合Vnew和Enew来描述所得到的最小生成树。
就是不断从新的顶点集合(初始只有一个顶点)中选取与其相邻的不在新顶点集合中的顶点的所有相连边中权值最短的边加入到新边集合中,该顶点加入新顶点集合。这样得到的新顶点集合和新边集合就是最小生成树。
代码
- 数据结构
- 集合
V :最开始的所有顶点集合 - 集合
Vnew :存放已经加入到最小生成树的顶点 - 一维数组
lowcost :记录V中所有顶点离最小生成树Vnew 的最短距离(已加入最小生成树的顶点则记为-1 ) - 一维数组
visit :记录v中顶点的访问情况(1 已加入最小生成树) - 集合
Enew :只是求最小权值的话就无需记录边了 - 步骤
V 中随机选择一个顶点加入Vnew ,更新lowcost 和visit 。- 重复以下步骤直到所有顶点的
visit 置为-1 :
- 遍历
lowcost ,寻找最小值,将对应顶点加入Vnew ,更新该顶点的visit 为-1 ,更新所有V 中未加入最小生成树的顶点的lowcost 值
- code1(通过样例65/72:超时)//待分析
#include "bits/stdc++.h"
using namespace std;
const int maxn = 0x7f7f7f;
class Solution {
public:
int ManhattanDistance(int x, int y) {
return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
}
int Prim(int start) {
vector<int> vNew = {start};
vector<int> lowCost(n, maxn);
lowCost[start] = -1;
int result = 0;
while (1) {
int curMinCost = maxn;
int curPoint = -1;
for (int i = 0; i < vNew.size(); ++i) {
for (int j = 0; j < n; ++j) {
lowCost[j] = min(lowCost[j], ManhattanDistance(vNew[i], j));
if (lowCost[j] != -1 && lowCost[j] < curMinCost) {
curMinCost = lowCost[j];
curPoint = j;
}
}
}
if (curPoint != -1) {
vNew.emplace_back(curPoint);
result += curMinCost;
lowCost[curPoint] = -1;
} else {
break;
}
}
return result;
}
int minCostConnectPoints(vector<vector<int>>& points) {
this->points = points;
n = points.size();
return Prim(0);
}
private:
vector<vector<int>> points;
int n;
};
- code2
实际只用了lowCost ,顶点直接用下标记录,是否已加入生成树可用lowCost 值是否为-1 判断,因为权值(曼哈顿距离)不会为负值; 整数最大值使用内置INT_MAX #include "bits/stdc++.h"
using namespace std;
class Solution {
public:
int ManhattanDistance(int x, int y) {
return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
}
int Prim(int start) {
vector<int> lowCost(n, INT_MAX);
int result = 0;
lowCost[start] = -1;
for (int i = 0; i < n; ++i) {
lowCost[i] = min(lowCost[i], ManhattanDistance(i, start));
}
for (int i = 0; i < n - 1; ++i) {
int minIndex = -1;
int minValue = INT_MAX;
for (int j = 0; j < n; ++j) {
if (lowCost[j] == -1) {
continue;
}
if (lowCost[j] < minValue) {
minValue = lowCost[j];
minIndex = j;
}
}
lowCost[minIndex] = -1;
result += minValue;
for (int j = 0; j < n; ++j) {
lowCost[j] = min(lowCost[j], ManhattanDistance(minIndex, j));
}
}
return result;
}
int minCostConnectPoints(vector<vector<int>>& points) {
this->points = points;
n = points.size();
return Prim(0);
}
private:
vector<vector<int>> points;
int n;
};
二、Kruskal算法
算法描述
Kruskal 算法是一种贪心算法,其思想很简单:
- 首先对所有的边进行排序,然后从小到大依次遍历每条边,同时判断每条边是否同源(属于同一个联通分量)
- 如果同源,跳过;
- 如果不同源,将两个连通分量合并,直到所有顶点属于同一个连通分量
很明显就是并查集
与Prim算法比较:
- Prim算法是以顶点为基础(每次寻找离Vnew最近的顶点);
- Kruskal算法是以边为基础,每次从边集合中寻找最小的边(不管两个顶点属于V还是Vnew),然后判断该边的两个顶点是否同源(属于同一个连通分量)。
代码
#include "bits/stdc++.h"
using namespace std;
class UnionFind {
public:
UnionFind(int n) {
pre.resize(n);
for (int i = 0; i < n; ++i) {
pre[i] = i;
}
}
int Find(int x) {
return x == pre[x] ? x : Find(pre[x]);
}
void Union(int x, int y) {
pre[Find(x)] = Find(y);
}
private:
vector<int> pre;
};
struct Eage {
int start;
int end;
int value;
Eage(int start, int end, int value) : start(start), end(end), value(value) {}
};
static bool Cmp(const Eage& a, const Eage& b) {
return a.value < b.value;
}
class Solution {
public:
int ManhattanDistance(vector<vector<int>>& points, int x, int y) {
return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
}
int minCostConnectPoints(vector<vector<int>>& points) {
int n = points.size();
vector<Eage> eages;
int result = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
eages.emplace_back(Eage(i, j, ManhattanDistance(points, i, j)));
}
}
sort(eages.begin(), eages.end(), Cmp);
UnionFind uf(n);
int m = eages.size();
int curLen = 1;
for (const auto& eage : eages) {
if (uf.Find(eage.start) != uf.Find(eage.end)) {
result += eage.value;
uf.Union(eage.start, eage.end);
++curLen;
if (curLen == n) {
break;
}
}
}
return result;
}
};
参考:
|