【PTA数据结构 | C语言版】最小生成树的唯一性

本专栏持续输出数据结构题目集,欢迎订阅。

文章目录

题目

给定一个带权无向图,如果是连通图,则至少存在一棵最小生成树,有时最小生成树并不唯一。本题就要求你计算最小生成树的总权重,并且判断其是否唯一。

输入格式:
首先第一行给出两个整数:无向图中顶点数 N(≤500)和边数 M。随后 M 行,每行给出一条边的两个端点和权重,格式为“顶点1 顶点2 权重”,其中顶点从 1 到N 编号,权重为正整数。题目保证最小生成树的总权重不会超过 2^30。

输出格式:
如果存在最小生成树,首先在第一行输出其总权重,第二行输出“Yes”,如果此树唯一,否则输出“No”。如果树不存在,则首先在第一行输出“No MST”,第二行输出图的连通集个数。

输入样例 1:
5 7
1 2 6
5 1 1
2 3 4
3 4 3
4 1 7
2 4 2
4 5 5

输出样例 1:
11
Yes

输入样例 2:
4 5
1 2 1
2 3 1
3 4 2
4 1 2
3 1 3

输出样例 2:
4
No

输入样例 3:
5 5
1 2 1
2 3 1
3 4 2
4 1 2
3 1 3

输出样例 3:
No MST
2

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_N 501
#define MAX_M 125000  // 最大边数:500*499/2 ≈ 124750
#define INF 0x3f3f3f3f

// 边的结构体
typedef struct {
    int u, v, weight;
} Edge;

// 并查集结构
int parent[MAX_N];

// 查找根节点(带路径压缩)
int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]);
    }
    return parent[x];
}

// 比较函数:按权重升序排序,权重相同则按顶点对升序排序
int compareEdges(const void* a, const void* b) {
    Edge* e1 = (Edge*)a;
    Edge* e2 = (Edge*)b;
    if (e1->weight != e2->weight) {
        return e1->weight - e2->weight;
    }
    if (e1->u != e2->u) {
        return e1->u - e2->u;
    }
    return e1->v - e2->v;
}

// Kruskal算法计算最小生成树权重,并收集关键边
int kruskal(Edge* edges, int m, int n, Edge* mstEdges, int* mstSize) {
    // 初始化并查集
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
    }
    
    // 排序边
    qsort(edges, m, sizeof(Edge), compareEdges);
    
    int totalWeight = 0;
    *mstSize = 0;
    
    // 遍历所有边,构建最小生成树
    for (int i = 0; i < m; i++) {
        int u = edges[i].u;
        int v = edges[i].v;
        int w = edges[i].weight;
        
        int rootU = find(u);
        int rootV = find(v);
        
        if (rootU != rootV) {
            parent[rootU] = rootV;
            totalWeight += w;
            mstEdges[*mstSize] = edges[i];
            (*mstSize)++;
        }
    }
    
    return totalWeight;
}

// 检查最小生成树是否唯一
int isUnique(Edge* edges, int m, int n, int mstWeight, Edge* mstEdges, int mstSize) {
    // 尝试移除每条 MST 边,检查是否还能形成相同权重的 MST
    for (int i = 0; i < mstSize; i++) {
        // 初始化并查集
        for (int j = 1; j <= n; j++) {
            parent[j] = j;
        }
        
        int currentWeight = 0;
        int edgesUsed = 0;
        
        // 遍历所有边,但跳过当前要移除的 MST 边
        for (int j = 0; j < m; j++) {
            // 跳过当前要移除的 MST 边
            if (edges[j].u == mstEdges[i].u && edges[j].v == mstEdges[i].v && 
                edges[j].weight == mstEdges[i].weight) {
                continue;
            }
            
            int u = edges[j].u;
            int v = edges[j].v;
            int w = edges[j].weight;
            
            int rootU = find(u);
            int rootV = find(v);
            
            if (rootU != rootV) {
                parent[rootU] = rootV;
                currentWeight += w;
                edgesUsed++;
                
                if (edgesUsed == n - 1) {
                    break;
                }
            }
        }
        
        // 如果能形成相同权重的 MST,则原 MST 不唯一
        if (edgesUsed == n - 1 && currentWeight == mstWeight) {
            return 0;  // 不唯一
        }
    }
    
    return 1;  // 唯一
}

// 计算图的连通分量数量
int countConnectedComponents(Edge* edges, int m, int n) {
    // 初始化并查集
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
    }
    
    // 遍历所有边,合并连通分量
    for (int i = 0; i < m; i++) {
        int u = edges[i].u;
        int v = edges[i].v;
        int rootU = find(u);
        int rootV = find(v);
        if (rootU != rootV) {
            parent[rootU] = rootV;
        }
    }
    
    // 统计连通分量数量
    int count = 0;
    for (int i = 1; i <= n; i++) {
        if (parent[i] == i) {
            count++;
        }
    }
    
    return count;
}

int main() {
    int N, M;
    scanf("%d %d", &N, &M);
    
    Edge edges[MAX_M];
    for (int i = 0; i < M; i++) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        // 确保u < v,方便后续处理
        if (u > v) {
            int temp = u;
            u = v;
            v = temp;
        }
        edges[i].u = u;
        edges[i].v = v;
        edges[i].weight = w;
    }
    
    // 检查图是否连通,并计算最小生成树
    Edge mstEdges[MAX_N];  // 存储MST的边
    int mstSize;
    int mstWeight = kruskal(edges, M, N, mstEdges, &mstSize);
    
    // 判断是否存在MST
    if (mstSize != N - 1) {
        // 不存在MST,计算连通分量数量
        int components = countConnectedComponents(edges, M, N);
        printf("No MST\n");
        printf("%d\n", components);
    } else {
        // 存在MST,判断是否唯一
        int unique = isUnique(edges, M, N, mstWeight, mstEdges, mstSize);
        printf("%d\n", mstWeight);
        printf("%s\n", unique ? "Yes" : "No");
    }
    
    return 0;
}
### Kruskal算法PTA 7-16 最小生成树问题中的实现与解题思路 Kruskal算法的核心思想是通过排序边的权重,并利用并查集来判断边是否会导致环路,从而逐步构建最小生成树。以下是具体实现和解题思路的详细说明: #### 解题思路 1. **输入处理**:读取顶点数 \(N\) 和边数 \(M\),然后读取每条边的信息(包括两个端点和权重)。确保输入格式正确。 2. **排序**:将所有边按照权重从小到大进行排序。这一步可以通过标准库函数如 `std::sort` 实现[^3]。 3. **初始化并查集**:为每个顶点初始化一个独立的集合,初始时每个顶点的父节点为其自身。 4. **边的选择**:遍历排序后的边列表,对于每条边,检查其两个端点是否属于同一个集合。如果属于不同集合,则将这条边加入生成树中,并合并这两个集合;否则跳过这条边以避免形成环路。 5. **结果输出**:当选择的边数达到 \(N-1\) 时,生成树构建完成,输出生成树的总权重。 #### 代码实现 以下是一个完整的C++代码示例,适用于PTA 7-16 最小生成树问题: ```cpp #include <iostream> #include <algorithm> using namespace std; const int MAX = 505; // 最大顶点数 struct Edge { int u, v, w; // 边的两个端点和权重 } edges[MAX * MAX]; int parent[MAX]; // 并查集数组 // 并查集查找函数 int find_set(int x) { if (parent[x] != x) { parent[x] = find_set(parent[x]); // 路径压缩 } return parent[x]; } // Kruskal算法主函数 int kruskal(int N, int M) { // 按权重对边进行排序 sort(edges, edges + M, [](const Edge &a, const Edge &b) -> bool { return a.w < b.w; }); // 初始化并查集 for (int i = 1; i <= N; ++i) { parent[i] = i; } int total_weight = 0; // 最小生成树的总权重 int edge_count = 0; // 已选择的边数 for (int i = 0; i < M && edge_count < N - 1; ++i) { int u = edges[i].u; int v = edges[i].v; int w = edges[i].w; int pu = find_set(u); int pv = find_set(v); if (pu != pv) { // 如果两个端点不在同一集合 parent[pu] = pv; // 合并集合 total_weight += w; // 累加权重 edge_count++; // 边计数增加 } } if (edge_count == N - 1) { // 判断是否构成生成树 return total_weight; } else { return -1; // 若无法构成生成树,返回-1 } } int main() { int N, M; cin >> N >> M; for (int i = 0; i < M; ++i) { cin >> edges[i].u >> edges[i].v >> edges[i].w; } int result = kruskal(N, M); if (result == -1) { cout << "impossible" << endl; } else { cout << result << endl; } return 0; } ``` #### 代码说明 1. **结构体定义**:使用 `Edge` 结构体存储每条边的两个端点和权重。 2. **并查集实现**:通过递归实现路径压缩的 `find_set` 函数,确保查找效率[^4]。 3. **排序**:使用 C++ 的 `std::sort` 函数对边按权重升序排列。 4. **生成树构建**:通过遍历排序后的边列表,结合并查集判断边是否可以加入生成树,直到选择的边数达到 \(N-1\)。 #### 注意事项 - 如果图不连通,最终选择的边数会小于 \(N-1\),此时应输出 `"impossible"`[^5]。 - 输入数据可能较大,因此需要优化并查集操作以提高效率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值