IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【贪心法】C++实现使用Kruskal算法构建最小生成树(包括查并集操作的实现) -> 正文阅读

[数据结构与算法]【贪心法】C++实现使用Kruskal算法构建最小生成树(包括查并集操作的实现)

Kruskal算法简介:

(1)将所有边按权值从小到大排序;
(2)选出权值最小的边,如果这条边的两个端点不属于同一个集合,就将这条边添加到最小生成树里面;
(3)重复(2)直到选出了 节 点 数 ? 1 节点数-1 ?1 条边;

如果最后选出的边数小于 节 点 数 ? 1 节点数-1 ?1 ,说明该无向图不能构建出最小生成树。


查并集操作简介:
这里我们假设集合是用树来表示的,也就是每个节点都有一个父节点根节点的父节点是自身)。
因此通过不断往上一个集合里面的点的父节点,最后都可以找到根节点,也就意味着每个集合是可以用父节点来唯一表示的。

查操作: 查询某个节点属于哪个集合,基于上述的假设,该问题可以转化为查询该节点的根节点。

并操作: 将两个集合合并,自然而然的做法就是将其中一个集合的根节点设置成另一个集合的根节点的子节点。注意,通常情况下我们会将小集合的根节点设置成大节点的子节点,因为小集合的树的深度一般较浅,添加到大集合之后对查询操作比较友好。

查询优化: 基于上述的查操作,我们当然希望每个子节点的父节点就是根节点,这样一次就能得到查询结果。因此,在进行查操作之后,我们可以将查询节点的父节点、查询节点的父节点的父节点等一系列点的父节点设置成根节点。


下面给出一份具有详细注释的源代码,可以很好地帮助大家理解上述算法。

//
//  main.cpp
//  KruskalAlalgorithm
//
//  Created by 胡昱 on 2021/12/22.
//

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

// 定义所需用到的全局变量
const int MAX_N = 500;  // 顶点的最大数量
int father[MAX_N + 1];      // 记录每一个顶点的父节点,用以查并集



// 边类
struct Edge {
    int u;  // 一个顶点
    int v;  // 另一个顶点
    int w;  // 边的权重
};

// 比较两条边的权重大小
int compareEdgesByW(const Edge& a, const Edge& b) {
    return a.w < b.w;
}

// 判断两条边是否为重边
bool operator == (const Edge&a, const Edge& b) {
    return (a.u == b.u) && (a.v == b.v);
}

// 查某个点舒服哪个集合(也就是根节点是哪个),同时顺便进行路径压缩
int find(int x) {
    
    // 子节点的父节点肯定不是自己,根节点的父节点肯定是自己,可以利用这个来寻找根节点
    int nowFather = x;
    while(father[nowFather] != nowFather) {
        nowFather = father[nowFather];
    }
    
    // 路径压缩,也就是让刚才查询中涉及到的点的父节点都设置成根节点
    int i = x, j;
    while(father[i] != nowFather) {
        
        // 记录下一个需要更新的节点
        j = father[i];
        
        // 将当前节点的父节点设置成根节点
        father[i] = nowFather;
        
        // 处理下一个节点
        i = j;
    }
    
    return nowFather;
}

// 合并两个集合
// 讲道理将小树(小集合)添加到大树(大集合)里面会比较好,但是在这边我们不做考虑
void unionSets(int u, int v) {
    int uFather = find(u);
    int vFather = find(v);
    
    // 如果这两个点所属集合的根节点是一致的,说明已经同属一个集合
    // 否则将这两个节点所属集合合并
    if(uFather != vFather) {
        
        // 将u所属集合的根节点变成v所属集合的根节点的子节点
        father[uFather] = vFather;
    }
}

int main(int argc, const char * argv[]) {
    // 共T组测试用例
    int T;
    cin >> T;
    while((T--) > 0) {
        // 输入顶点数n、边数E
        int n, E;
        cin >> n >> E;
        
        // 创建边数组并输入边
        vector<Edge> edges;
        for(int ei = 0; ei < E; ++ei) {
            Edge edge;
            cin >> edge.u >> edge.v >> edge.w;
            edges.push_back(edge);
        }
        // 先对边数组排序,再进行去重,最后更新
        sort(edges.begin(), edges.end(), compareEdgesByW);
        edges.erase(unique(edges.begin(), edges.end()), edges.end());
        E = (int)edges.size();
        
        // 初始化父节点集合,此时所有顶点的父节点都是自身,即每个顶点都是单独的一棵树
        // 注意:顶点是从1开始编号的
        for(int vi = 1; vi <= n; ++vi) {
            father[vi] = vi;
        }
        
        // 开始Kruskal算法
        
        // 用于记录结果,也就是最小生成树权值
        int result = 0;
        
        // K用于记录已经添加的边数
        int k = 0;
        
        // 每次都从边数组中取出最小的边(因为我们已经排好序了,所以直接取出即可)
        for(Edge edge: edges) {
            
            // 如果k等于n - 1,说明已经取了足够的边了,因为n个点需要n-1条边连接
            if(k >= n - 1) {
                break;
            }
            
            // 如果这条边的两个点不在同一个集合,那么这条边需要添加到最小生成树里面
            // 添加指的就是将这两个点的集合合并
            if(find(edge.u) != find(edge.v)) {
                unionSets(edge.u, edge.v);
                result += edge.w;
                ++k;
            }
        }
        
        // Kruskal算法结束
        
        // 如果k小于n - 1,说明不能生成最小生成树
        // 也可以判断所有点是否在同一个集合,但时间复杂度较高
        if(k < n - 1) {
            result = -1;
        }
        
        // 输出结果
        cout << result << endl;
    }
    return 0;
}

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-12-23 15:58:58  更:2021-12-23 15:59:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 16:30:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码