1584. 连接所有点的最小费用

1584. 连接所有点的最小费用
一道最小生成树的算法题,适合用于巩固 \(Kruskal\) 算法。

1. 代码

// 交换两个整数的值(用于按秩合并时的变量交换)
void swap(int* a, int* b) {
    int tmp = *a;
    *a = *b, *b = tmp;
}

// 定义边结构体
struct Edge {
    int len, x, y; // len: 边的权重(距离),x 和 y: 边连接的两个点
};

// 排序比较函数:按边长 len 从小到大升序排列
int cmp(const void* a, const void* b) { // 不允许通过这个指针修改它指向的数
    return ((struct Edge*)a)->len - ((struct Edge*)b)->len;
}

// 并查集:查找根节点(包含路径压缩优化,将树的高度降低)
int find(int* f, int x) { 
    return f[x] == x ? x : (f[x] = find(f, f[x])); 
} // f[i] = i 父亲是自己,说明他就是掌门人

// 并查集:合并两个集合
int unionSet(int* f, int* rank, int x, int y) {
    int fx = find(f, x), fy = find(f, y);
    if (fx == fy) {
        return false; // 如果已经在同一个集合中,说明连接它们会形成环,返回失败
    }
    // 按秩合并优化:始终将较小的树合并到较大的树上
    if (rank[fx] < rank[fy]) {
        swap(&fx, &fy);
    }
    rank[fx] += rank[fy];
    f[fy] = fx;
    return true; // 合并成功
}

int minCostConnectPoints(int** points, int pointsSize, int* pointsColSize) {
    int n = pointsSize;
    // 初始化边集数组:n 个点最多有 n*(n-1)/2 条边
    struct Edge edges[(n + 1) * n / 2];
    int edgesSize = 0;

    // 第一步:计算所有点对之间的曼哈顿距离,构建完整的边集
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            edges[edgesSize].x = i;
            edges[edgesSize].y = j;
            // 曼哈顿距离公式:|x1 - x2| + |y1 - y2|
            edges[edgesSize++].len = abs(points[i][0] - points[j][0]) +
                                     abs(points[i][1] - points[j][1]);
        }
    }

    // 第二步:将所有边按距离从小到大排序(贪心策略的核心)
    qsort(edges, edgesSize, sizeof(struct Edge), cmp);

    // 第三步:并查集初始化
    int f[n], rank[n];
    for (int i = 0; i < n; i++) {
        f[i] = i;    // 初始时每个点的父节点是它自己
        rank[i] = 1; // 初始秩(集合大小)为 1
    }

    int ret = 0; // 累计最小生成树的总权重
    int num = 1; // 记录当前已连接的点数(当连通点数等于 n 时,边数为 n-1)

    // 第四步:遍历排序后的边,依次尝试加入
    for (int i = 0; i < edgesSize; i++) {
        // 如果这条边连接了两个尚未连通的集合
        if (unionSet(f, rank, edges[i].x, edges[i].y)) {
            ret += edges[i].len; // 记录权重
            num++;               // 增加已连接的点数
            if (num == n) {      // 优化:如果所有点都已连通,提前结束
                break;
            }
        }
    }
    return ret;
}

下面进行拆解。

2. 工具函数

// 交换两个整数的值(用于按秩合并时的变量交换)
void swap(int* a, int* b) {
    int tmp = *a;
    *a = *b, *b = tmp;
}

// 定义边结构体
struct Edge {
    int len, x, y; // len: 边的权重(距离),x 和 y: 边连接的两个点
};

// 排序比较函数:按边长 len 从小到大升序排列
int cmp(const void* a, const void* b) { // 不允许通过这个指针修改它指向的数
    return ((struct Edge*)a)->len - ((struct Edge*)b)->len;
}

3. 并查集

// 并查集:查找根节点(包含路径压缩优化,将树的高度降低)
int find(int* f, int x) { 
    return f[x] == x ? x : (f[x] = find(f, f[x])); 
}

// 并查集:合并两个集合
int unionSet(int* f, int* rank, int x, int y) {
    int fx = find(f, x), fy = find(f, y);
    if (fx == fy) {
        return false; // 如果已经在同一个集合中,说明连接它们会形成环,返回失败
    }
    // 按秩合并优化:始终将较小的树合并到较大的树上
    if (rank[fx] < rank[fy]) {
        swap(&fx, &fy);
    }
    rank[fx] += rank[fy];
    f[fy] = fx;
    return true; // 合并成功
}

  • f[] 是一个集合。
  • f[i] 表示节点的父亲,如果 f[i] = i,也就是父亲为自己,说明没有父亲,此时 i 是根结点。
  • 路径压缩的关键代码:f[x] = find(f, f[x])
  • 并查集的合并在王道书上有讲过,见 [[【第六章】图]]的 \(Kruskal\) 算法部分。

4. 主程序


int minCostConnectPoints(int** points, int pointsSize, int* pointsColSize) {
    int n = pointsSize;
    // 初始化边集数组:n 个点最多有 n*(n-1)/2 条边
    struct Edge edges[(n + 1) * n / 2];
    int edgesSize = 0;

    // 第一步:计算所有点对之间的曼哈顿距离,构建完整的边集
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            edges[edgesSize].x = i;
            edges[edgesSize].y = j;
            // 曼哈顿距离公式:|x1 - x2| + |y1 - y2|
            edges[edgesSize++].len = abs(points[i][0] - points[j][0]) +
                                     abs(points[i][1] - points[j][1]);
        }
    }

    // 第二步:将所有边按距离从小到大排序(贪心策略的核心)
    qsort(edges, edgesSize, sizeof(struct Edge), cmp);

    // 第三步:并查集初始化
    int f[n], rank[n];
    for (int i = 0; i < n; i++) {
        f[i] = i;    // 初始时每个点的父节点是它自己
        rank[i] = 1; // 初始秩(集合大小)为 1
    }

    int ret = 0; // 累计最小生成树的总权重
    int num = 1; // 记录当前已连接的点数(当连通点数等于 n 时,边数为 n-1)

    // 第四步:遍历排序后的边,依次尝试加入
    for (int i = 0; i < edgesSize; i++) {
        // 如果这条边连接了两个尚未连通的集合
        if (unionSet(f, rank, edges[i].x, edges[i].y)) {
            ret += edges[i].len; // 记录权重
            num++;               // 增加已连接的点数
            if (num == n) {      // 优化:如果所有点都已连通,提前结束
                break;
            }
        }
    }
    return ret;
}
posted @ 2026-05-16 00:46  syn_tax  阅读(3)  评论(0)    收藏  举报