代码随想录算法训练营Day63|最小生成树算法prim和kruskal、kama 53.寻宝

news/2024/7/23 10:12:59 标签: 算法, 数据结构

最小生成树

        最小生成树(Minimum Spanning Tree,MST)算法是图论中的一个重要概念,它是指在加权无向图中,找到一个边的子集,构成一个树形结构,该子集包含图中所有的顶点,同时保证所有边的权值之和最小。这个构成的树就称为最小生成树。

        常用的最小生成树算法主要有两种:

  1. 普里姆算法(Prim's algorithm):
  • 从图中的一个顶点开始,逐步增加新的边和顶点,直到形成最小生成树。
  • 算法的关键步骤是选择最小的边来扩展当前的树。
  • 算法适合于边稠密的图。

     2.克鲁斯卡尔算法(Kruskal's algorothm):

  • 他按边的权重顺序(从小到大)选择边,并检查是否加入这条边后会形成环。
  • 如果不会形成换,则将这条边加入到最小生成树中。
  • 算法适合于边稀疏的图。

        这两种算法能够有效地找到加权无向图的最小生成树,但它们在处理图的顶点和边的关系上有所不同。普里姆算法从顶点出发,而克鲁斯卡尔算法从边出发。

        最小生成树算法在实际应用中非常重要,例如在设计和构建物理网络(如电信网络、计算机网络、道路建设等)时,通过最小生成树算法可以以最小的成本构建出覆盖所有节点的网络结构。此外,它们在许多优化问题中也扮演着关键角色,如电路设计、网络构建等。

寻宝

53. 寻宝(第七期模拟笔试) (kamacoder.com)

prim算法

prim算法的核心有三步

  1. 选距离生成树最近节点
  2. 最近节点加入生成树
  3. 更新非生成树节点到生成树的距离(更新minDist数组)

minDist数组是用来记录每一个节点距离最小生成树的最近距离。

minDist数组的值初始化为最大数,本题初始化为10001(根据题目设定)。

vector<int>minDist(V+1,10001)//共有V个顶点,这里为了与顶点编号对应,数组从1到v,0弃用

第一步需要选距离生成树最近节点,在开始时,并没有生成树,也没有节点,考虑随便加入一个节点(注意对于最小生成树来说,每个节点都是必要的,所以可以随便加入),这里先加入节点1.

然后对与1节点相连的节点,更新minDist数组,即将权赋值到minDist数组上。

之后就是不断选择距离最小生成树最近的节点,加入到最小生成树,更新非生成树节点到生成树的距离,直到最后所有节点都在生成树中,最后对minDist数组的第二位到末尾相加,即为最小权和。

具体步骤详解可以参考代码随想录 (programmercarl.com)

#include<vector>
#include<iostream>
#include<climits>
using namespace std;

int main(){
    int V,E; // V 表示顶点的数量,E 表示边的数量
    cin>>V>>E; // 输入顶点和边的数量

    int x,y,k; // x, y, k 分别表示边的两个顶点和权重
    // 创建一个 V+1 行 V+1 列的二维向量 grid,初始化所有值为 10001(表示无穷大)
    vector<vector<int>>grid(V + 1,vector<int>(V + 1,10001));

    // 读入每条边的信息,并更新 grid 中的权重值
    for(int i = 0; i < E;i++){
        cin >>x>>y>>k; // 输入边的两个顶点和权重
        grid[x][y] = k; // 由于是无向图,所以两个方向的权重相同
        grid[y][x] = k;
    }

    // 初始化两个辅助向量,minDist 用于记录从已选树顶点到每个顶点的最小距离,isInTree 用于标记顶点是否已加入最小生成树
    vector<int> minDist(V + 1, 10001); // 初始化所有最小距离为无穷大
    vector<int> isInTree(V + 1, 0); // 初始化所有顶点都未加入最小生成树

    //Prim 算法的核心部分,选择 V-1 条边加入最小生成树
    for(int i = 1; i < V; i ++){
        int cur = -1; // 当前选择的顶点
        int minVal = INT_MAX; // 从已选树顶点到当前顶点的最小距离,初始化为无穷大

        // 遍历所有顶点,找到未加入树且距离最小的顶点
        for(int j = 1; j <=V;j++){
            if(!isInTree[j] and minDist[j] < minVal){
                minVal = minDist[j];
                cur = j;
            }
        }
        isInTree[cur] = 1; // 将当前顶点标记为已加入最小生成树

        // 更新从已选树顶点到其他顶点的最小距离
        for(int j = 1; j <= V;j++){
            if(!isInTree[j] and grid[cur][j] < minDist[j]){
                minDist[j] = grid[cur][j];
            }
        }
    }

    // 计算最小生成树的总权重
    int result = 0;
    for(int i= 2;i<=V;i++){ // 从 2 开始,因为 1 是起始顶点,其最小距离为 0,不应该计入总权重
        result += minDist[i];
    }
    cout<<result<<endl; // 输出最小生成树的总权重

}

算法的时间复杂度为O(V^2),空间复杂度为O(V^2)。

Kruskal算法

相比于prim算法,Kruskal算法侧重边的权重顺序(从小到大)选择边,并检查加入该边后是否会形成环,若不会形成环,则将这条边加入到最小生成树中。该算法适合于边稀疏的图。

算法的步骤如下:

  1. 排序:将图中的所有边按权重从小到大排序。
  2. 初始化:创建一个森林,其中每个顶点都是一个独立的树。
  3. 选择边:按排序后的顺序选择边,对于每条边(v,w),执行一下操作
    1. 使用一个并查集来检查v和w是否在同一个集合中。
    2. 若v和w不在同一个集合中,则将这条边加入的生成树中,并将v和w所在集合合并
  4. 重复:直到森林F中包含V-1条边,这是森林F就是最小生成树。(连接v个顶点最少需要v-1条边)
  5. 结束:构造最小生成树完成。

详解可以看 代码随想录 (programmercarl.com)

代码如下:

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

using namespace std;

// 定义边结构体,包含两个顶点和一个权重
struct Edge{
    int l, r, val;
};

// 定义顶点的最大数量
int n = 10001;

// 定义并查集数组,用于存储每个顶点的父节点
vector<int> father(n,-1);

// 初始化并查集,每个顶点的父节点设置为自己
void init(){
    for(int i = 0; i < n; i++)
        father[i] = i;
}

// 查找函数,用于找到顶点u的根节点,同时进行路径压缩
int find(int u){
    return u == father[u]? u:father[u] = find(father[u]);
}

// 合并函数,用于合并两个集合
void join(int u, int v){
    int m = find(u); // 找到顶点u的根节点
    int n = find(v); // 找到顶点v的根节点
    if(m == n) return; // 如果根节点相同,说明已经在同一个集合中
    father[n] = m; // 将顶点v的根节点设置为顶点u的根节点
}

int main(){
    int v, e; // v表示顶点数量,e表示边数量
    vector<Edge> edges; // 存储所有边的数组
    cin >> v >> e; // 输入顶点数量和边数量
    int v1, v2, val; // v1, v2表示边的两个顶点,val表示边的权重
    for(int i = 0 ; i < e; i++){ // 读取所有边的信息
        cin >> v1 >> v2 >> val; // 输入边的两个顶点和权重
        edges.push_back({v1, v2, val}); // 将边添加到数组中
    }

    // 按边的权重对边进行排序
    sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
        return a.val < b.val;
    });

    init(); // 初始化并查集
    int result = 0; // 用于存储最小生成树的总权重
    for (Edge edge : edges) { // 遍历所有边
        int x = find(edge.l); // 找到边左顶点的根节点
        int y = find(edge.r); // 找到边右顶点的根节点
        if (x != y) { // 如果根节点不同,说明加入这条边不会形成环
            result += edge.val; // 将边的权重加到总权重上
            join(x, y); // 合并两个集合
        }
    }
    cout << result << endl; // 输出最小生成树的总权重
    return 0;
} 

Kruskal算法的时间复杂度主要取决于边的排序和并查集操作。排序的时间复杂度是 O(E log E),其中 E 是边的数量。在边排序之后,算法会遍历每条边,并执行并查集操作,这个操作的时间复杂度接近 O(E log V),其中 V 是顶点的数量。因此,Kruskal算法的总时间复杂度是 O(E log E) 或 O(E log V),取决于哪个是主导项。

Kruskal算法的空间复杂度主要取决于并查集数据结构,它需要存储每个顶点的父节点信息,因此空间复杂度是 O(V)。

Kruskal算法适用于边比较稀疏的图,因为它的时间复杂度与边的数量成对数关系,而不是与顶点的平方成对数关系,如prim算法那样。

   


http://www.niftyadmin.cn/n/5546440.html

相关文章

书生大模型实战营(暑假场)-入门岛-第一关

书生大模型实战营暑假场重磅开启&#xff01;&#xff0c;这场学习路线看起来很好玩呀&#xff0c;闯关学习既能学到知识又有免费算力可得&#xff0c;太良心啦。感兴趣的小伙伴赶快一起报名学习吧&#xff01;&#xff01;&#xff01; 关卡任务 好的&#xff0c;我们废话不多…

【康复学习--LeetCode每日一题】724. 寻找数组的中心下标

题目&#xff1a; 给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标&#xff0c;其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端&#xff0c;那么左侧数之和视为 0 &#xff0c;因为在下标的左侧不…

二次开发报错Request method ‘GET’ not supported

环境&#xff1a;springboot3 问题复刻&#xff1a; 前端上传图片的时候&#xff0c;出现了这个报错&#xff0c;离谱 解决方法&#xff1a;修改本地上传文件的路径 没错&#xff0c;路径错误的报错居然是这个&#xff0c;真顶级 是因为你的配置文件中profile这个属性的路径在…

Pytest单元测试系列[v1.0.0][Pytest基础]

Pytest安装与配置 和Unittest一样&#xff0c;Pytest是另一个Python语言的单元测试框架&#xff0c;与Unittest相比它的测试用例更加容易编写、运行方式更加灵活、报错信息更加清晰、断言写法更简洁并且它可以运行有unittest和nose编写的测试用例。 Pytest 安装 启动命令行&…

了解安全端口

安全端口的定义和重要性 安全端口是指在网络通信中&#xff0c;用于特定服务或应用程序的端口&#xff0c;这些端口通常被设计为在网络层面提供额外的安全性。安全端口的选择和配置对于保护网络资源免受未经授权的访问和攻击至关重要。 常见的安全端口及其用途 以下是一些常见…

adb 常用的命令总结

1、adb logcat 抓取日志 adb logcat > d:\log.txt Ctrlc 结束日志抓取 adb logcat -c > d:\log.txt 清空旧日志 发生Native Crash 时&#xff0c;抓取错误报告 adb logcat -b crash 抓取筛选后的日志&#xff1a; adb logcat -s AndroidRuntime > d:\log…

解决了一个java Bug:Exception in thread “main“ java.lang.NullPointerException

写代码&#xff0c;遇到了个问题。 很纳闷&#xff0c;跟着人家写的代码。只能去查资料。 赶紧去找&#xff0c;自己的代码 逆天&#xff0c;赶紧改&#xff01; 成功了&#xff01;&#xff01;&#xff01;

vue前端实现导出页面为word(两种方法)

将vue页面导出为word文档&#xff0c;不用写模板&#xff0c;直接导出即可。 第一种方法(简单版) 第一步&#xff1a;安装所需依赖 npm install html-docx-js -S npm install file-saver -S第二步&#xff1a;创建容器&#xff0c;页面使用方法 注意&#xff1a;在当前页面引…