首页 > 编程语言 >算法日记 47 day 最小生成树(prim,kruskal)

算法日记 47 day 最小生成树(prim,kruskal)

时间:2024-12-11 20:58:51浏览次数:5  
标签:prim 47 kruskal 最小 距离 生成 minDist 节点

今天主要是针对最小生成树的两种算法。

用题目来举例

题目:寻宝

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

题目描述

在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。

不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来(注意:这是一个无向图)。 

给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。

输入描述

第一行包含两个整数V 和 E,V代表顶点数,E代表边数 。顶点编号是从1到V。例如:V=2,一个有两个顶点,分别是1和2。

接下来共有 E 行,每行三个整数 v1,v2 和 val,v1 和 v2 为边的起点和终点,val代表边的权值。

输出描述

输出联通所有岛屿的最小路径总距离

输入示例

7 11
1 2 1
1 3 1
1 5 2
2 6 1
2 4 2
2 3 2
3 4 1
4 5 1
5 6 2
5 7 1
6 7 1

输出示例

6

提示信息

数据范围:
2 <= V <= 10000;
1 <= E <= 100000;
0 <= val <= 10000;

如下图,可见将所有的顶点都访问一遍,总距离最低是6.

 

题目分析:

        实际上就是寻找最小连通子图,以最小的成本(边的权值)将图中所有节点链接到一起。

那么,对于n个结点,就需要n-1条边来将他们连起来。所以我们就需要在所有的边里面选出n-1条边。来看看两种算法怎么选出这些边。

prim算法

prim算法呢,主要维护的结点的信息,他的思路类似于贪心的策略,每次选代价最小的结点加入到最小生成树中,所以prim算法的三个步骤就是

1.寻找离最小生成树最近的结点

2.将最近结点加入最小生成树

3.更新结点离最小生成树最近的距离

所以我们用一个数组minDist来记录每一个结点距离最小生成树的距离,初始为结点数加1即可。为了方便和结点对应,这个数组我们也开始从1开始使用。

所以他的初始状态为

那么开始构造最小生成树,这边直接选取第一个结点加入最小生成树就好了

1、prim三部曲,第一步:选距离生成树最近节点

选择距离最小生成树最近的节点,加入到最小生成树,刚开始还没有最小生成树,所以随便选一个节点加入就好(因为每一个节点一定会在最小生成树里,所以随便选一个就好),那我们选择节点1 (符合遍历数组的习惯,第一个遍历的也是节点1)

2、prim三部曲,第二步:最近节点加入生成树

此时 节点1 已经算最小生成树的节点。

3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组)

接下来,我们要更新所有节点距离最小生成树的距离,如图:

注意下标0,我们就不管它了,下标 1 与节点 1 对应,这样可以避免大家把节点搞混。

此时所有非生成树的节点距离 最小生成树(节点1)的距离都已经跟新了 。

  • 节点2 与 节点1 的距离为1,比原先的 距离值10001小,所以更新minDist[2]。
  • 节点3 和 节点1 的距离为1,比原先的 距离值10001小,所以更新minDist[3]。
  • 节点5 和 节点1 的距离为2,比原先的 距离值10001小,所以更新minDist[5]。

注意图中我标记了 minDist数组里更新的权值,是哪两个节点之间的权值,例如 minDist[2] =1 ,这个 1 是 节点1 与 节点2 之间的连线,清楚这一点对最后我们记录 最小生成树的权值总和很重要。

1、prim三部曲,第一步:选距离生成树最近节点

选取一个距离 最小生成树(节点1) 最近的非生成树里的节点,节点2,3,5 距离 最小生成树(节点1) 最近,选节点 2(其实选 节点3或者节点2都可以,距离一样的)加入最小生成树。

2、prim三部曲,第二步:最近节点加入生成树

此时 节点1 和 节点2,已经算最小生成树的节点。

3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组)

接下来,我们要更新节点距离最小生成树的距离,如图:

此时所有非生成树的节点距离 最小生成树(节点1、节点2)的距离都已经跟新了 。

  • 节点3 和 节点2 的距离为2,和原先的距离值1 小,所以不用更新。
  • 节点4 和 节点2 的距离为2,比原先的距离值10001小,所以更新minDist[4]。
  • 节点5 和 节点2 的距离为10001(不连接),所以不用更新。
  • 节点6 和 节点2 的距离为1,比原先的距离值10001小,所以更新minDist[6]。

剩下的步骤就是重复这个过程,我这里就不写了

看看具体的代码实现

#include<iostream>
#include<vector>
#include <climits>
 
using namespace std;
 
int main(){
    int v,e;
    int x,y,value;
     
    cin>>v>>e;
    vector<vector<int>>grid(v+1,vector<int>(v+1,10001));
    while(e--){
        cin>>x>>y>>value;
        //无向图
        grid[x][y]=value;
        grid[y][x]=value;
    }
     
    //记录每一个节点到最小生成树节点的最小距离
    vector<int> minDist(v+1,10001);
     
    //记录节点是否在最小生成树中
    vector<bool> isInTree(v+1,false); 
     
    //n个节点,需要n-1条边来连通
    //遍历构建n-1条边(所有数组从1开始,方便对应)
    for(int i=1;i<v;i++){//n-1条边
     
        //1.prim三部曲,选取里最小生成树最近的节点,加入最小生成树
         
        int cur_idx=-1;//选取的节点
        int min_Value=INT_MAX;
         
        //遍历minDist,寻找最近节点
        for(int j=1;j<=v;j++){
            if(!isInTree[j]&&minDist[j]<min_Value){
                cur_idx=j;
                min_Value=minDist[j];
            }
        }
         
        //2.将最小节点加入到最小生成树中
        isInTree[cur_idx]=true;
         
        //3.更新minDist,更新为离最小生成树最近的距离
        for(int j=1;j<=v;j++){
            //更新的条件: 1.不在最小生成树中 2.这个节点和cur节点的距离,比之前里最小生成树的距离小
            if(!isInTree[j]&&grid[cur_idx][j]<minDist[j]){
                minDist[j]=grid[cur_idx][j];
            }
        }
    }
     
    //构建完成最小生成树
     
    //统计结果
    int result = 0;
    for (int i = 2; i <= v; i++) { // 不计第一个顶点,因为统计的是边的权值,v个节点有 v-1条边
        result += minDist[i];
    }
    cout << result << endl;
}

 krusal算法

不同于prim算法的处理结点,kruskal选择处理的是边,

来看看它的具体思路

krusal的思路:

  • 边的权值排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合

可以看到,krusal 算法主要在于判断两个点是否在同一个集合中(最小生成树),这一部分,就需要用到并查集了,

看看图解

将图中的边按照权值有小到大排序,这样从贪心的角度来说,优先选 权值小的边加入到 最小生成树中。

排序后的边顺序为[(1,2) (4,5) (1,3) (2,6) (3,4) (6,7) (5,7) (1,5) (3,2) (2,4) (5,6)]

(1,2) 表示节点1 与 节点2 之间的边。权值相同的边,先后顺序无所谓。

开始从头遍历排序后的边

选边(1,2),节点1 和 节点2 不在同一个集合,所以生成树可以添加边(1,2),并将 节点1,节点2 放在同一个集合。

选边(4,5),节点4 和 节点 5 不在同一个集合,生成树可以添加边(4,5) ,并将节点4,节点5 放到同一个集合。

继续这个思路,就可以把所有的点都加到最小生成树中了,看看具体的代码实现

#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;
    }
}
 int find(int u){
     return u==father[u]?u:father[u]=find(father[u]);
 }
  
 void join(int u,int v){
     u=find(u);
     v=find(v);
     if(u==v) return;
     father[v]=u;
 }
  
 bool isSame(int u,int v){
     u=find(u);
     v=find(v);
     return u==v;
 }
  
 int main(){
     int v,e;
     int v1,v2,val;
     int result_val=0;
     cin>>v>>e;
     vector<Edge> edges; //记录边
     while(e--){
         cin>>v1>>v2>>val;
         edges.push_back({v1,v2,val});
     }
      
    // 执行Kruskal算法
    // 按边的权值对边进行从小到大排序
    sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
            return a.val < b.val;
    });
     
    init();//初始化并查集
     
    // 从头开始遍历边
    for (Edge edge : edges) {
        // 并查集,搜出两个节点的祖先
        int x = find(edge.l);
        int y = find(edge.r);
 
        // 如果祖先不同,则不在同一个集合
        if (x != y) {
            result_val += edge.val; // 这条边可以作为生成树的边
            join(x, y); // 两个节点加入到同一个集合
        }
    }
    cout << result_val << endl;
    return 0;
 }

所以,总的来看,最小生成树的两种算法各有各的侧重,那么对于一些边少节点多的情况我们使用Krusal算法,反之Prim算法即可。

对于更详细的解析与其他语言的代码块,可以去代码随想录上查看。

代码随想录 (programmercarl.com)

已刷题目:142

标签:prim,47,kruskal,最小,距离,生成,minDist,节点
From: https://blog.csdn.net/weixin_70808146/article/details/144408886

相关文章

  • 最小生成树(prim和kruskal)学习笔记
    有两个求最小生成树的算法,prim算法和kruskal算法。这两种算法都可以处理边权为负的情况,并且可以处理有负权回路的情况。接下来会分析一下两个算法的区别。prim算法这个算法思路主要是不断向最小生成树中添加点,而这个添加的点是距离生成树最近的点。这个算法主要用在稠密图里......
  • 大数据-247 离线数仓 - 电商分析 拉链表的分析与构建与回滚
    点一下关注吧!!!非常感谢!!持续更新!!!Java篇开始了!目前开始更新MyBatis,一起深入浅出!目前已经更新到了:Hadoop(已更完)HDFS(已更完)MapReduce(已更完)Hive(已更完)Flume(已更完)Sqoop(已更完)Zookeeper(已更完)HBase(已更完)Redis(已更完)Kafka(已更完)Spark(已更完)Flink(已更完)ClickHouse(已更完)Kudu(......
  • springboot市社保局社保信息管理与分析系统-计算机毕业设计源码03479
     摘  要 社保是保障公民基本生活的重要制度。随着社保参保人数的增加和社保政策的不断调整,传统的手工操作已不能满足社保管理的需要。因此,设计和实现一个高效、安全、可靠的社保信息管理和分析系统对于有效管理和优化社保工作具有重要现实意义。市社保局社保信息管理与分......
  • 最小生成树算法(Prim算法 + Kruskal算法)
    最小生成树(MST)算法完整版万字原文见史上最全详解图数据结构加权无向图中,最小生成树是一个包含图中所有节点的子图树———>包含图中所有节点最小———>树中的边权之和最小1.Prim算法(最小生成树)算法原理:1.贪心算法2.从一个起始点开始,逐步选择与当前......
  • 「Mac玩转仓颉内测版47」小学奥数篇10 - 数列求和
    本篇将通过Python和Cangjie双语实现数列求和的计算。通过这个题目,学生将学会如何通过公式法和循环法求解等差数列与等比数列的和。关键词小学奥数Python+Cangjie数列求和一、题目描述编写一个程序,计算等差数列和等比数列的和。用户输入首项、公差/公比以及项数,......
  • CF2040D Non Prime Tree 题解
    CF992Div2D-solution给定一个\(n\)个节点的树,你可以不重复地给树的节点填\(1\sim2n\)之间的数,求一种构造方案,使得每两个相邻的节点上的数之差的绝对值为合数。我们规定每次填的数只会变大(就是在以某种方法遍历的时候后面的数一定比前面的数大)。现在我们假设填到了\(u\)......
  • 深入理解PrimeFaces DataTable的懒加载分页机制
    在现代Web应用开发中,处理大量数据时,性能和用户体验是至关重要的。PrimeFacesDataTable组件提供了一种懒加载(lazyloading)机制,允许我们分批次加载和显示大量数据,而不是一次性加载所有数据。本文将通过一个具体的实例,详细解释如何利用PrimeFaces、JPA、Hibernate和H2内存数据......
  • XCVM1302-3HSEVFVB1369通过业界领先的 DDR 内存接口实现高数据吞吐量 - AMD Versal Pr
    XCVM1302-2MLIVSVF1369XCVM1302-2MSEVSVF1369XCVM1302-2MSIVFVB1369XCVM1302-2MSIVSVF1369XCVM1302-3HSEVFVB1369明佳达Versal自适应SoC兼具可编程逻辑和加速引擎的灵活处理能力,以及先进的内存和接口技术,可为各类应用实现定制化、强大的异构加速。VersalPrime系列是基......
  • LCR 047. 二叉树剪枝(中等)(主站814)
    https://leetcode.cn/problems/pOCWxh/https://leetcode.cn/problems/binary-tree-pruning/难度:☆☆☆题目:给定一个二叉树根节点root,树的每个节点的值要么是0,要么是1。返回移除了所有不包含1的子树的原二叉树。节点node的子树为node本身,以及所有node的后......
  • springboot玩具回收与租借系统-计算机毕业设计源码47829
    目 录摘要1绪论1.1研究背景1.2课题意义2 玩具回收与租借系统系统分析2.1可行性分析2.1.1技术可行性分析2.1.2经济可行性分析2.1.3法律可行性分析2.2系统功能分析2.2.1功能性分析2.2.2非功能性分析2.3系统用例分析2.4系统流程分析2......