leetcode 1584.连接所有点的最小费用(C/C++/Java/python)

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语言参考网址

【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;
    }
};

C++参考网址

【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]);
    }
}

Java参考网址

【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

python参考网址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值