AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

2025.6.8 学习笔记(最小生成树)

作者: 作者的头像   jwh ,  2025-06-08 11:57:32 · 安徽 ,  所有人可见 ,  阅读 2


0


最小生成树

472879_e9ae71a00c-屏幕截图-2025-03-29-224136.png

prim算法

Prim算法求最小生成树
朴素版prim算法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define PII pair<int,int>
const int N=1e5+10;
int n,m;
int e[N*2],ne[N*2],h[N],w[N*2],idx; // 邻接表存储图
int dist[N];      // dist[i] 表示节点i到当前生成树的最小距离
bool st[N];       // st[i] 标记节点i是否已加入生成树

// 添加无向边
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

int prim()
{
    memset(dist,0x3f,sizeof dist); // 初始化所有距离为无穷大
    dist[1]=0;                     // 从节点1开始,距离设为0
    int t;
    int ans=0;                     // 存储最小生成树的总权重
    for(int i=0;i<n;++i)           // 循环n次,每次加入一个节点
    {
        t=-1;
        // 寻找未加入生成树中dist最小的节点
        for(int j=1;j<=n;++j){
            if(st[j]) continue;    // 跳过已加入的节点
            else if(t==-1||dist[j]<dist[t]) t=j; // 更新最小节点
        }
        // 如果最小距离仍是无穷大,说明图不连通
        if(dist[t]>=0x3f3f3f3f) return 0x3f3f3f3f;
        ans+=dist[t];             // 累加当前边的权重
        st[t]=true;               // 标记节点t已加入生成树(修正位置:在更新前标记)

        // 遍历节点t的所有邻接边
        for(int j=h[t];j!=-1;j=ne[j]){
            int k=e[j];           // 邻接节点k
            // 如果节点k未加入生成树且当前边权小于dist[k],则更新
            if (!st[k] && dist[k] > w[j]) {
                dist[k] = w[j];
            }
        }
    }
    return ans;
}

int main()
{
    memset(h,-1,sizeof(h));      // 初始化邻接表头指针
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);              // 无向图添加双向边
        add(b,a,c);
    }
    int t=prim();
    if(t==0x3f3f3f3f) cout<<"impossible"<<endl;
    else cout<<t<<endl;
    return 0;
}

堆优化版prim算法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define PII pair<int,int>  // 定义pair类型,用于存储(距离, 节点)

const int N = 1e5 + 10;     // 最大节点数
int n, m;                   // n: 节点数, m: 边数
int e[N * 2], ne[N * 2], h[N], w[N * 2], idx;  // 邻接表存储图
int dist[N];                // dist[i] 表示节点i到当前生成树的最小距离
bool st[N];                 // st[i] 标记节点i是否已加入生成树

// 添加无向边
void add(int a, int b, int c) {
    e[idx] = b;         // 存储边的终点
    w[idx] = c;         // 存储边的权重
    ne[idx] = h[a];     // 链表指针指向下一个节点
    h[a] = idx++;       // 更新头指针
}

// 优先队列优化的Prim算法
int prim() {
    memset(dist, 0x3f, sizeof dist);  // 初始化所有距离为无穷大
    priority_queue<PII, vector<PII>, greater<PII>> q;  // 小顶堆,存储(距离, 节点)
    q.push({0, 1});     // 从节点1开始,初始距离为0
    dist[1] = 0;        // 设置节点1的距离为0

    int ans = 0;        // 最小生成树的总权重
    int cnt = 0;        // 已加入生成树的节点数

    while (!q.empty()) {
        auto t = q.top();  // 获取堆顶元素(当前距离生成树最近的节点)
        q.pop();

        int dis = t.first;   // 当前节点到生成树的距离
        int ver = t.second;  // 当前节点编号

        // 如果节点已加入生成树,跳过重复处理
        if (st[ver]) continue;

        // 首次访问该节点:加入生成树
        ans += dis;         // 累加边权
        cnt++;              // 节点计数增加
        st[ver] = true;     // 标记节点已加入生成树

        // 遍历当前节点的所有邻接边
        for (int i = h[ver]; i != -1; i = ne[i]) {
            int j = e[i];  // 邻接节点j

            // 如果邻接节点尚未加入生成树且当前边权小于已知最小距离
            if (!st[j] && dist[j] > w[i]) {
                dist[j] = w[i];          // 更新节点j到生成树的最小距离
                q.push({dist[j], j});    // 将更新后的距离和节点加入堆
            }
        }
    }

    // 如果加入的节点数不足n,说明图不连通
    if (cnt != n) return 0x3f3f3f3f;  // 返回特殊值表示无解
    return ans;  // 返回最小生成树总权重
}

int main() {
    memset(h, -1, sizeof h);  // 初始化邻接表头指针为-1
    cin >> n >> m;

    // 读入所有边并构建无向图
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);  // 添加a->b的边
        add(b, a, c);  // 添加b->a的边(无向图双向添加)
    }

    int t = prim();  // 执行Prim算法

    // 输出结果
    if (t == 0x3f3f3f3f) 
        cout << "impossible" << endl;  // 无生成树
    else 
        cout << t << endl;  // 输出权重总和

    return 0;
}

kruskal算法

Kruskal算法求最小生成树

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define PII pair<int,int>
const int N=1e5+10;  // 定义常量N,表示最大节点数
int n,m;             // n-节点数, m-边数

// 定义边的结构体,包含起点a、终点b和权重c
struct edge
{
    int a,b,c;
};
edge s[200010];  // 存储所有边的数组
int p[200010];   // 并查集父节点数组

// 边排序比较函数:按权重升序排列
bool cmp(edge a,edge b)
{
    return a.c<b.c;
}

// 并查集查找函数(带路径压缩)
int find(int a)
{
    if(p[a]!=a) 
        p[a]=find(p[a]);  // 递归查找并压缩路径
    return p[a];          // 返回根节点
}

// Kruskal算法实现最小生成树
int kruskal()
{
    sort(s+1,s+m+1,cmp);          // 按边权重升序排序
    for(int i=1;i<=n;i++) p[i]=i; // 初始化并查集

    int res=0;   // 存储最小生成树的总权重
    int cnt=0;   // 计数已选边的数量

    // 遍历所有边
    for(int i=1;i<=m;i++){
        int aa=s[i].a;  // 当前边的起点
        int bb=s[i].b;  // 当前边的终点
        int cc=s[i].c;  // 当前边的权重

        int pa=find(aa);  // 查找起点所在集合
        int pb=find(bb);  // 查找终点所在集合

        // 如果不在同一集合,则选择该边
        if(pa!=pb){
            p[pa]=pb;    // 合并集合
            res+=cc;     // 累加权重
            cnt++;       // 增加已选边计数
        }
    }

    // 判断连通性:最小生成树应有n-1条边
    if(cnt<n-1) 
        return 0x3f3f3f3f;  // 不连通时返回特殊值
    else 
        return res;         // 连通时返回总权重
}

int main()
{
    cin>>n>>m;  // 输入节点数和边数

    // 读入所有边
    for(int i=1;i<=m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        s[i]={a,b,c};  // 存储边信息
    }

    int t=kruskal();  // 调用Kruskal算法

    // 输出结果:不连通输出"impossible",否则输出总权重
    if(t==0x3f3f3f3f) 
        cout<<"impossible"<<endl;
    else 
        cout<<t<<endl;

    return 0;
}

0 评论

App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息