算法第四版- 4.3

算法第四版- 4.3

最小生成树MST

0.序

Prim算法,以顶点为单元,与图中边数无关,比较适合于稠密图
Kruskal算法,以边为顶点,时间主要取决于边数,适合稀疏图
下面代码的来源
采用的是曼哈顿距离。

1.Prim算法

class Solution {
public:
    int prim(vector<vector<int> >& points, int start) {
        int n = points.size();
        int res = 0;
        // 1. 将points转化成邻接矩阵, 这一步可有可无
        vector<vector<int> > g(n, vector<int>(n));
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
                g[i][j] = dist;
                g[j][i] = dist;
            }
        }
        // 记录V[i]到Vnew的最近距离
        vector<int> lowcost(n, INT_MAX);
        // 记录V[i]是否加入到了Vnew
        vector<int> v(n, -1);

        // 2. 先将start加入到Vnew
        v[start] = 0;
        for (int i = 0; i < n; i++) {
            if (i == start) continue;
            lowcost[i] = g[i][start];
        }

        // 3. 剩余n - 1个节点未加入到Vnew,遍历
        for (int i = 1; i < n; i++) {
            // 找出此时V中,离Vnew最近的点
            int minIdx = -1;
            int minVal = INT_MAX;
            for (int j = 0; j < n; j++) {
                if (v[j] == 0) continue;
                if (lowcost[j] < minVal) {
                    minIdx = j;
                    minVal = lowcost[j];
                }
            }
            
            // 将该点加入Vnew,更新lowcost和v
            res += minVal;
            v[minIdx] = 0;
            lowcost[minIdx] = -1;

            // 更新集合V中所有点的lowcost
            for (int j = 0; j < n; j++) {
                if (v[j] == -1 && g[j][minIdx] < lowcost[j]) {
                    lowcost[j] = g[j][minIdx];
                }
            }
        }
        return res;

    }
    int minCostConnectPoints(vector<vector<int>>& points) {
        return prim(points,0);

    }
};

楼上是O(n^2)
再补充一个堆优化的,O(mlogn),m为边的数目

class Solution {
public:
    int prim(vector<vector<int> >& points, int start) {
        int n = points.size();
        if (n == 0) return 0;
        int res = 0;

        // 将points转化成邻接表
        vector<vector<int> > g(n);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == j) continue;
                g[i].push_back(j);
                g[j].push_back(i);
            }
        }
        
        // 记录V[i]到Vnew的最近距离
        vector<int> lowcost(n, INT_MAX);
        // 记录V[i]是否加入到了Vnew
        vector<int> v(n, -1);

        // 格式:<距离, 下标>
        priority_queue<pair<int, int>, vector<pair<int, int> >, greater<> > pq;
        pq.push(make_pair(0, start));
        
        while (!pq.empty()) {
            auto [dist, i] = pq.top();
            pq.pop();
            if (v[i] == 0) continue;
            v[i] = 0;
            res += dist;

            for (int k = 0; k < g[i].size(); k++) {
                int j = g[i][k];
                int w = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
                if (v[j] == -1 && lowcost[j] > w) {
                    lowcost[j] = w;
                    pq.push(make_pair(w, j));
                }
            }
        }
        return res;

    }
    int minCostConnectPoints(vector<vector<int>>& points) {
        return prim(points, 0);  
    }
};

2. Kruskal算法

依次选择最小的边,看是否形成环
时间复杂度O(m log(m) + m α(m) )

class Djset {
public:
    vector<int> parent; // 记录节点的根
    vector<int> rank;   // 记录根节点的深度(用于优化)
    vector<int> size;   // 记录每个连通分量的节点个数
    vector<int> len;    // 记录每个连通分量里的所有边长度
    int num;            // 记录节点个数
    Djset(int n): parent(n), rank(n), len(n, 0), size(n, 1), num(n) {
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    int find(int x) {
        // 压缩方式:直接指向根节点
        if (x != parent[x]) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    int merge(int x, int y, int length) {
        int rootx = find(x);
        int rooty = find(y);
        if (rootx != rooty) {
            if (rank[rootx] < rank[rooty]) {
                swap(rootx, rooty);
            }
            parent[rooty] = rootx;
            if (rank[rootx] == rank[rooty]) rank[rootx] += 1;
            // rooty的父节点设置为rootx,同时将rooty的节点数和边长度累加到rootx,
            size[rootx] += size[rooty];
            len[rootx] += len[rooty] + length;
            // 如果某个连通分量的节点数 包含了所有节点,直接返回边长度
            if (size[rootx] == num) return len[rootx];
        }
        return -1;
    }
};
struct Edge {
    int start; // 顶点1
    int end;   // 顶点2
    int len;   // 长度
};

class Solution {
public:
    int minCostConnectPoints(vector<vector<int>>& points) {
        int res = 0;
        int n = points.size();
        Djset ds(n);
        vector<Edge> edges;
        // 建立点-边式数据结构
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                Edge edge = {i, j, abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1])};
                edges.emplace_back(edge);
            }
        }
        // 按边长度排序
        sort(edges.begin(), edges.end(), [](const auto& a, const auto& b) {
            return a.len < b.len;
        });

        // 连通分量合并
        for (auto& e : edges) {
           res = ds.merge(e.start, e.end, e.len);
           if (res != -1) return res;
        }
        return 0;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值