P3366 【模板】最小生成树

解题思路

  1. 最小生成树(MST)概念

    • 在连通无向图中,找到一棵包含所有顶点的树,使得所有边的权值之和最小

    • 如果图不连通,则不存在最小生成树

  2. Prim算法

    • 贪心算法,从单个顶点开始逐步扩展MST

    • 维护一个dis数组记录当前MST到各顶点的最小距离

    • 每次选择距离最近的未加入顶点,更新其邻居的距离

    • 时间复杂度:O(n²),适合稠密图

  3. Kruskal算法

    • 贪心算法,按边权从小到大考虑

    • 使用并查集来判断是否形成环

    • 每次选择不会形成环的最小边加入MST

    • 时间复杂度:O(m log n),适合稀疏图

  4. 两种算法的比较

    • Prim需要邻接表存储图,Kruskal直接处理边列表

    • Prim适合稠密图,Kruskal适合稀疏图

    • 在本题数据范围下(N≤5000, M≤2×10⁵),Kruskal更优

  5. 实现注意事项

    • 判断图是否连通:Prim看加入顶点数是否为n,Kruskal看是否选了n-1条边

    • 无向图处理:需要添加双向边

    • 初始化:距离数组初始化为无穷大,并查集初始化为各自独立

  6. 优化空间

    • Prim可以用优先队列优化到O(m log n)

    • Kruskal的并查集可以进一步优化路径压缩和按秩合并

这两种算法都是求解最小生成树的经典方法,根据图的稠密程度选择合适的算法可以提高效率。本题中由于边数可能很大,Kruskal算法更为合适。

第一部分:Kruskal算法实现

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
struct node {
    int x, y, z;  // 边的两个顶点和权重
};
node t[N];        // 存储所有边
int n, m;         // n:顶点数, m:边数
int f[N];         // 并查集父节点数组

int find(int x) { // 并查集查找函数
    if(f[x] != x) f[x] = find(f[x]);
    return f[x];
}

void merge(int x, int y) { // 并查集合并函数
    int fx = find(x), fy = find(y);
    f[fy] = fx;
}

bool cmp(node a, node b) { // 边按权重从小到大排序
    return a.z < b.z;
}

void kruskal() {
    for(int i = 1; i <= n; i++) f[i] = i; // 初始化并查集
    sort(t + 1, t + 1 + m, cmp);         // 对边按权重排序
    
    int sum = 0, ans = 0; // sum:已选边数, ans:最小生成树总权值
    for(int i = 1; i <= m; i++) {
        int x = t[i].x, y = t[i].y;
        if(find(x) != find(y)) {  // 如果两个顶点不在同一集合
            merge(x, y);          // 合并集合
            ans += t[i].z;        // 累加权值
            sum++;
            if(sum == n - 1) {    // 已选n-1条边,完成MST
                cout << ans;
                return;
            }
        }
    }
    cout << "orz";  // 无法形成MST
}

int main() {
    cin >> n >> m;
    for(int i = 1; i <= m; i++) 
        cin >> t[i].x >> t[i].y >> t[i].z; // 读入所有边
    kruskal();
    return 0;
}

 

第二部分:Prim算法实现

#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N = 1e5 + 10, inf = 0x3f3f3f3f;
vector<pii> g[N];  // 邻接表存储图,g[u]存储与u相连的所有边(顶点,权重)
int n, m;          // n:顶点数, m:边数
int dis[N], vis[N]; // dis:当前最小生成树到各顶点的最小距离, vis:标记是否已加入MST

void prim() {
    memset(dis, inf, sizeof(dis)); // 初始化所有距离为无穷大
    dis[1] = 0;                   // 从顶点1开始构建MST
    int sum = 0, ans = 0;         // sum:已加入的顶点数, ans:最小生成树总权值
    
    for(int i = 1; i <= n; i++) {
        // 寻找未加入MST的顶点中距离最小的
        int minn = inf, pos;
        for(int j = 1; j <= n; j++)
            if(dis[j] < minn && vis[j] == 0)
                minn = dis[j], pos = j;
        
        if(minn == inf) break;    // 图不连通
        vis[pos] = 1;             // 标记为已加入
        sum++;
        ans += minn;              // 累加权值
        
        // 更新与新加入顶点相邻的顶点的最小距离
        for(int j = 0; j < g[pos].size(); j++) {
            int v = g[pos][j].first, z = g[pos][j].second;
            if(dis[v] > z) dis[v] = z;
        }
    }
    
    if(sum != n) {                // 如果加入的顶点数不足n,说明图不连通
        cout << "orz"; return;
    }
    cout << ans;                  // 输出最小生成树总权值
}

int main() {
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        int x, y, z; cin >> x >> y >> z;
        g[x].push_back({y,z});    // 无向图,添加双向边
        g[y].push_back({x,z});
    }
    prim();
    return 0;
}

 

prim的优先队列优化

 

#include<bits/stdc++.h>
#define pii pair<int,int>  // 定义pair类型别名,存储(权重,顶点)
using namespace std;

const int N = 1e5 + 10, inf = 0x3f3f3f3f;  // 最大顶点数和无穷大常量
vector<pii> g[N];  // 邻接表存储图,g[u]存储与u相连的所有边(顶点,权重)
int n, m;         // n:顶点数, m:边数
int dis[N];       // dis数组记录各顶点到MST的最小距离
int vis[N];       // vis数组标记顶点是否已加入MST

void prim() {
    memset(dis, inf, sizeof(dis));  // 初始化所有距离为无穷大
    dis[1] = 0;                    // 从顶点1开始,距离设为0
    int sum = 0, ans = 0;          // sum:已加入顶点数, ans:最小生成树总权值
    
    // 使用最小堆优化,按距离排序,存储(距离,顶点)
    priority_queue<pii, vector<pii>, greater<pii>> q;
    q.push({0, 1});  // 初始放入顶点1,距离0
    
    while(!q.empty()) {
        int x = q.top().second;  // 取出当前距离最小的顶点
        q.pop();
        
        if(vis[x]) continue;  // 如果已访问过则跳过(避免重复处理)
        vis[x] = 1;           // 标记为已加入MST
        ans += dis[x];        // 累加边权
        sum++;                // 已加入顶点数+1
        
        // 遍历当前顶点的所有邻边
        for(int i = 0; i < g[x].size(); i++) {
            int y = g[x][i].first;   // 邻接顶点
            int z = g[x][i].second;  // 边权重
            
            // 如果找到更小的连接距离
            if(!vis[y] && dis[y] > z) {
                dis[y] = z;  // 更新距离
                q.push({dis[y], y});  // 将新距离加入优先队列
                // 注意:这里可能会重复加入同一顶点,但通过vis标记可以过滤
            }
        }
    }
    
    // 输出结果
    if(sum != n) cout << "orz";  // 如果加入顶点数不足n,说明图不连通
    else cout << ans;            // 否则输出最小生成树总权值
}

int main() {
    cin >> n >> m;
    // 读入图数据
    for(int i = 1; i <= m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        g[x].push_back({y, z});  // 无向图,添加双向边
        g[y].push_back({x, z});
    }
    prim();  // 执行Prim算法
    return 0;
}

 

posted @ 2025-04-28 19:54  CRt0729  阅读(72)  评论(0)    收藏  举报