PS:算法并非原创,仅作个人学习记录使用,侵删
题目描述
题目链接
算法分析
看到题目之后我的算法思路是:最小生成树,将点都连接起来形成图,权重为距离,之后利用K算法或者P算法解答。
复习一下:
最小生成树的K算法和P算法都属于贪心算法的范畴,且都利用了最小生成树性质(最小生成树一定包含最小权边),两者都在不断地寻找最小边,但是K算法侧重于对边的分类,P算法侧重于对顶点的分类。
K算法:Kruskal算法,将边按照权重排序,并按照权重递增的顺序查看每一条边。对于选中的边,如果两端的顶点属于两个不同的连通分量,那么这个边被正式“录用”,否则,则抛开这条边,查看之后的边。
P算法:Prim算法,算法简述的话,就是将顶点分成俩部分,一部分顶点集叫做S,S里面的顶点都是最小生成树的组成顶点,而另一部分顶点集则是不包括S的其他顶点组成的。连接两部分顶点集的边构成一个集合,每次寻找权重最小的边,并且把这条边的顶点加入S中。重复这个过程,S不断扩大,直到和图的顶点集一样大小,此时根据之前选择的边,得到的就是最小生成树。
大佬们的解题算法里面,还有并查集算法。
并查集,yyds!(再一遍
并查集其实本质上和Kruskal算法相同,根据连通分量查找最小可选边,选择,累计最小生成树的权重和。
代码实现
【C】
/*
并查集
*/
int ret[10000000][3];//存储距离,边i的顶点是ret[i][1]和ret[i][2],距离为ret[i][0]
int map[1000000];//存储顶点所属队伍的队长。顶点i的队长是顶点map[i]。
int cmpfun(void* a, void* b){//排序函数
return ((int *)a)[0] - ((int *)b)[0];
}
int find(int* parent, int i) {//找i所属小队的队长
if (parent[i] == i)
return i;
return parent[i] = find(parent, parent[i]);
}
void Union(int* parent, int x, int y){//联合x,y所属的队伍
int xset = find(parent, x);
int yset = find(parent, y);
if (xset != yset)
parent[xset] = yset;
}
int minCostConnectPoints(int** points, int pointsSize, int* pointsColSize){//核心函数
int totalSize = 0;//所有边的个数
int sum = 0;
int max = pointsSize * pointsSize;
memset(ret, 0, sizeof(int) * max);
memset(map, 0, sizeof(int) * max);//存储
//并查集初始化
for (int i = 0; i < max; ++i) {
map[i] = i;
}
//算出所以可能的距离
for (int i = 0; i < pointsSize; ++i) {
for (int j = i + 1; j < pointsSize; ++j) {
ret[totalSize][1] = i;
ret[totalSize][2] = j;
ret[totalSize++][0] = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
}
}
//从小到大排序
qsort(ret, totalSize, sizeof(ret[0]), cmpfun);
//从小到大添加到集合里面,如果某个距离对应的点已经在并查集里面,跳过。
for (int i = 0; i < totalSize; ++i) {
int faterFirst = find(map, ret[i][1]);
int faterSecond = find(map, ret[i][2]);
if (faterFirst != faterSecond) {//如果两端属于不同的连通分量,“录用”这个点
Union(map, faterFirst, faterSecond);
sum += ret[i][0];
}
}
return sum;
}
【C++】
/*
最小生成树————Kruskal算法+并查集算法
*/
class DisjointSetUnion {//并查集类
private:
vector<int> f, rank;
int n;
public:
DisjointSetUnion(int _n) {//类的构造函数
n = _n;
rank.resize(n, 1);
f.resize(n);
for (int i = 0; i < n; i++) {
f[i] = i;
}
}
int find(int x) {//并查集中查找连通分量队长
return f[x] == x ? x : f[x] = find(f[x]);
}
int unionSet(int x, int y) {//合并两个连通分量
int fx = find(x), fy = find(y);
if (fx == fy) {
return false;
}
if (rank[fx] < rank[fy]) {
swap(fx, fy);
}
rank[fx] += rank[fy];
f[fy] = fx;
return true;
}
};
struct Edge {
int len, x, y;
Edge(int len, int x, int y) : len(len), x(x), y(y) {
}
};
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points) {
auto dist = [&](int x, int y) -> int {//计算距离
/*auto的原理就是根据后面的值,来自己推测前面的类型是什么。*/
return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
};
int n = points.size();
DisjointSetUnion dsu(n);//初始化并查集
vector<Edge> edges;//构造图需要的边集
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
edges.emplace_back(dist(i, j), i, j);
}
}
sort(edges.begin(), edges.end(), [](Edge a, Edge b) -> int { return a.len < b.len; });//将边按照从小到大进行排序
int ret = 0, num = 1;//ret计算累计权重和,num记录生成树中的顶点个数
for (auto& [len, x, y] : edges) {//遍历边集合
if (dsu.unionSet(x, y)) {//如果两个顶点属于不同的连通分量,选择该边
ret += len;
num++;
if (num == n) {//循环终止条件:生成树顶点已经不能再增加
break;
}
}
}
return ret;
}
};
【Java】
/*
最小生成树————Prim算法
*/
class Solution {
public int minCostConnectPoints(int[][] points) {
int n=points.length;
int[] dist=new int[n];//S集合和非S集合中的距离数组
Arrays.fill(dist, Integer.MAX_VALUE);//dist数组初始化,表示点之间没有联系
boolean[] added=new boolean[n];//判断某点是否被添加进S集合
int ans=0;//存储最终结果
for(int i=0; i<n; i++){//遍历顶点
int min=0;
for(int j=0; j<n; j++){//二次遍历寻找满足条件的边权最小的边对应的端点
if(!added[j]&&dist[j]<dist[min]){//j需要不属于S
min=j;//记录非S集合中选择中的端点
}
}
added[min]=true;//选中的端点加入S集合中
if(dist[min]!=Integer.MAX_VALUE)//如果选择的边有效
ans+=dist[min];
//加入新的端点,需要更新S集合和非S集合中的距离数组
for(int j=0; j<n; j++){
if(!added[j]){//如果j不属于S集合
dist[j]=Math.min(dist[j], mdist(points[min], points[j]));//
}
}
}
return ans;
}
int mdist(int[] x, int[] y){//计算距离
return Math.abs(x[0]-y[0])+Math.abs(x[1]-y[1]);
}
}
【python】
#贪心算法
class Solution:
def minCostConnectPoints(self, points):
n = len(points)
if n == 1:
return 0
res = 0 # 最终的输出结果
dis = [1e9] * n # 初始化从每个点出发的最短距离
cur = 0 # 当前点的下标
visited = set() # 标记访问集合
for i in range(n-1):
x, y = points[cur] # 取横纵坐标
visited.add(cur) # 该位置被访问
for j, point in enumerate(points):
# 被访问过,直接进入下一轮
if j in visited:
continue
dis[j] = min(dis[j], abs(point[0] - x) + abs(point[1] - y))
# 每一次都找出最小的dis,累加到ans上,然后重新初始化该位置的dis
s, cur = min((d, j) for j, d in enumerate(dis))
dis[cur] = 1e9
res += s
return res