最小生成树(prim算法和kruskal算法的模板练习)

Prim vs Kruskal:

  • Prim 算法:适合稠密图,利用优先队列维护可扩展边,直接从顶点构造生成树。
  • Kruskal 算法:适合稀疏图,按边权重排序后逐渐合并集合。

     

做法一:kruskal算法,并查集和元组tuple的运用:

Kruskal 算法思想:

  1. 边排序:将所有边按照权重从小到大排序。
  2. 并查集:用于管理节点间的连接情况。并查集可以有效判断两个节点是否已经连通,防止环的形成。
  3. 逐步构造最小生成树:从权重最小的边开始,若该边连接的两个节点不属于同一个集合,则将其加入生成树。

以下是ac代码:

#include<iostream>
#include<vector>
#include<tuple>
#include<algorithm>

using namespace std;

// 并查集中的查找函数,带路径压缩
int find(vector<int>& fx, int x) {
    if (fx[x] != x)
        fx[x] = find(fx, fx[x]); // 路径压缩
    return fx[x]; // 返回根节点
}

// 并查集中的合并函数
bool join(vector<int>& fx, vector<int>& rank, int x, int y) {
    int rootx = find(fx, x);
    int rooty = find(fx, y);
    if (rootx != rooty) {
        if (rank[rootx] > rank[rooty]) {
            fx[rooty] = rootx;
        } else if (rank[rootx] < rank[rooty]) {
            fx[rootx] = rooty;
        } else {
            fx[rooty] = rootx;
            rank[rootx]++;
        }
        return true;
    }
    return false;
}

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> fx(n); // 并查集,存储每个节点的父节点
    vector<int> rank(n, 0); // 并查集的秩(用于合并时平衡树的高度)
    
    vector<tuple<int, int, int>> t; // 存储边 (节点1, 节点2, 权重)
    
    // 读取输入的边
    for (int i = 0; i < m; i++) {
        int w, x, y;
        cin >> x >> y >> w;
        t.push_back(make_tuple(x - 1, y - 1, w)); // 0基索引
    }
    
    // 初始化并查集,每个节点的父节点初始化为自己
    for (int i = 0; i < n; i++) {
        fx[i] = i;
    }
    
    // 按照边的权重进行排序
    sort(t.begin(), t.end(), [](const tuple<int, int, int>& a, const tuple<int, int, int>& b) {
        return get<2>(a) < get<2>(b); // 按权重排序
    });
    
    int ans = 0, edges = 0;
    
    // Kruskal算法构造最小生成树
    for (auto& edge : t) {
        int weight, x, y;
        tie(x, y, weight) = edge; // 解包边的节点和权重
        
        // 如果两个节点不在同一个集合中,则合并它们
        if (join(fx, rank, x, y)) {
            ans += weight; // 加上该边的权重
            edges++;
        }
        
        // 如果已经构造出 n - 1 条边的生成树,提前退出
        if (edges == n - 1) {
            break;
        }
    }
    
    // 检查是否构造了 n - 1 条边的生成树,判断是否连通
    if (edges != n - 1) {
        cout << "orz" << endl;
    } else {
        cout << ans << endl;
    }
    
    return 0;
}

做法二:prim算法优先队列(小根堆)的使用:

Prim 算法思想:

  1. 初始化:从任意一个节点开始,将其加入生成树(标记为已访问)。
  2. 贪心选择:每次选择连接树中某个节点和未访问节点的最小权重边,加入生成树。
  3. 重复:重复上述步骤,直到所有节点都被访问。
  4. 判断连通性:如果最终生成树包含的边数不足 N−1N-1N−1,说明图不连通。

以下是ac代码:

#include <iostream>
#include <vector>
#include <queue>
#include <functional>

using namespace std;

const int INF = 0x7fffffff;  // 表示无穷大

// 定义一个结构体来表示边的信息
struct Edge {
    int to, weight;
    Edge(int _to, int _weight) : to(_to), weight(_weight) {}
    
    // 定义一个比较器,用于优先队列按权重排序
    bool operator>(const Edge& other) const {
        return weight > other.weight;
    }
};

int main() {
    int N, M;
    cin >> N >> M;

    // 邻接表存储图,g[i] 存储的是与节点 i 相连的边 (to, weight)
    vector<vector<Edge>> g(N);

    // 读取图的边
    for (int i = 0; i < M; ++i) {
        int x, y, z;
        cin >> x >> y >> z;
        x--; y--;  // 将节点编号转换为从 0 开始
        g[x].emplace_back(y, z);
        g[y].emplace_back(x, z);
    }

    // Prim's Algorithm
    vector<bool> visited(N, false);  // 标记节点是否已被访问
    priority_queue<Edge, vector<Edge>, greater<Edge>> pq;  // 优先队列,用于选取权重最小的边
    int mst_weight = 0;  // 最小生成树的总权重
    int edges_used = 0;  // 计数使用了多少条边
    
    // 从节点 0 开始
    pq.push(Edge(0, 0));  // 从 0 号节点开始,初始权重为 0

    // 执行 Prim 算法
    while (!pq.empty()) {
        Edge current = pq.top();
        pq.pop();

        int node = current.to;
        int weight = current.weight;

        // 如果当前节点已经被访问过,跳过
        if (visited[node]) continue;

        // 否则将其标记为已访问
        visited[node] = true;
        mst_weight += weight;
        edges_used++;

        // 遍历当前节点的邻居节点
        for (Edge& neighbor : g[node]) {
            if (!visited[neighbor.to]) {
                pq.push(Edge(neighbor.to, neighbor.weight));
            }
        }
    }

    // 判断是否生成了一棵包含所有节点的树
    if (edges_used == N) {
        cout << mst_weight << endl;
    } else {
        cout << "orz" << endl;  // 如果边数不足,说明图不连通
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值