首页 > 其他分享 >最小生成树模板

最小生成树模板

时间:2023-08-11 23:34:35浏览次数:30  
标签:int 最小 距离 生成 集合 INF 模板 dis

prim算法

算法思想:每次选定未进入集合中和集合距离最小的点,用该点更新其他点到集合的距离,直到可以判断出不存在最小生成树或所有点均已进入集合 下面虽然是两种写法,但是记忆时最好还是按照算法的思路来实现,也就是第2个代码。虽然会多一些边界处理,但是只要我们理解了算法思想即使忘记了代码也能够自己实现出来

/**
 * 不需要每次都用集合中的点去找距离集合最近的点
 * 可以维护每个点到集合的距离,可以节省很多时间
 * 
 * 每次找到距离集合最近的点,加入到集合中,然后用该点更新其它点到集合的距离,每次操作向集合中加入一个点,一共n个点所以一共需要迭代n次
 * 
 * 本题为稠密图,所以采用邻接矩阵存储
 */

// 不太符合算法思想的写法,但是可以减少一些判断条件
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int dis[N]; // 之前的dis表示某点距离起点的距离,此时表示某点距离集合的距离
bool vis[N]; // 表示某个点是否在集合中
int g[N][N];

int prim()
{
    memset(dis, 0x3f, sizeof dis);
    /**
     * 关于dis[1]的初始化问题
     * 按照该算法的逻辑来讲,不应该初始化这个点
     * dis[1] = 0表示1号点已经位于集合内了
     * 但是初始状态集合中应该为空
     * 不过这么写答案是正确的并且可以减少一下判断条件,只是和算法思想有些不符
     */
    dis[1] = 0;
     
    int res = 0;
    for (int i = 0; i < n; ++ i) // 每次将一点放入集合
    {
        // 找到未在集合且距集合最近的点
        int t = -1;
        for (int j = 1; j <= n; ++ j)
            if (!vis[j] && (t == -1 || dis[t] > dis[j]))
                t = j;
        
        // 将该点放入集合并累加答案
        vis[t] = true;
        if (dis[t] == INF) return -1; // 说明目前找到的距离集合最近的点已经是无穷远的点了,说明无法构成一棵最小生成树
        res += dis[t];
    
        // 用该点更新其它节点距离集合的距离
        for (int j = 1; j <= n; ++ j)
            dis[j] = min(dis[j], g[t][j]); // 因为此时t已经进入了集合,所以和t相连的j点距离集合的距离就是j点距离t点的距离
    }
     
    return res;
}
int main()
{
    cin >> n >> m;
    // memset(g, 0x3f, sizeof g);
    /**
     * 对于g的初始化问题
     * 朴素版dijkstra和Floyd都采用的是邻接矩阵
     * dijkstra全部初始为无穷大
     * Floyd自己到自己初始化为0,其它初始化为无穷大
     * 怎么初始化完全要看算法执行过程对这些数据的要求
     * 按照我们在prim中的写法dis[j] = min(dis[j], g[t][j])
     * 可以发现用到自己到自己距离时说明t已经进入集合了,那么它的dis正确与否已经无所谓了,并不会影响到其它点到集合的距离,所以自己到自己不用初始化也可以
     */
     while (m --)
     {
         int a, b, k;
         cin >> a >> b >> k;
         g[a][b] = g[b][a] = min(g[a][b], k);
     }
     
     int t = prim(); // t表示返回的最小生成树的树边权重之和,-1表示最小生成树不存在
     
     if (t == -1) cout << "impossible" << endl;
     else cout << t << endl;
     
     return 0;
}

// 符合算法思想的写法
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int dis[N];
bool vis[N];
int g[N][N];

int prim()
{
    memset(dis, 0x3f, sizeof dis); // 初始化dis[1] = 0并不会使答案发生改变,因为第一个点无论距离集合的距离为多少,总是会进入集合的,并且用它更新其它点时也不会用到自己的dis,所以无影响,但是从实际意义角度来讲不应该赋值0
    int res = 0;
    for (int i = 0; i < n; ++ i)
    {
        // 寻找距离集合距离最小的点
        // 所有距离都是无穷大,那么无穷大就是最小距离,我这思维定势了,觉得无穷大就不会是距离最小的点
        int t = -1;
        for (int j = 1; j <= n; ++ j)
            if (!vis[j] && (t == -1 || dis[t] > dis[j]))
                t = j;
                
        if (i && dis[t] == INF) return INF; // 不存在最小生成树的条件也是找到的最小距离都是无穷远,但是第一个点的距离肯定是INF,所以需要特判一下
        vis[t] = true;
        if (i) res += dis[t]; // 因为第一个点的dis是无穷大,所以不能累加到答案上,本身第一个点到集合的距离就是0,所以加不加无所谓
        
        for (int j = 1; j <= n; ++ j)
            dis[j] = min(dis[j], g[t][j]);
    }
    
    return res;
}
int main()
{
    memset(g, 0x3f, sizeof g); // 从使用g的位置可以看出,即使g[i][i]这种没更新答案也不会受到影响,其实更新了肯定不会错,如果不知道需不需要初始化那就写上,总不会错
    cin >> n >> m;
    while (m --)
    {
        int a, b, k;
        cin >> a >> b >> k;
        g[a][b] = g[b][a] = min(g[a][b], k);
    }
    
    int t = prim();

    if (t == INF) cout << "impossible" << endl;
    else cout << t << endl;
    
    return 0;
}

Kruskal算法

/**
 * Kruskal算法的理论思路
 * 首先对所有边从小到大排序,然后遍历所有边,如果该边的加入不会使得树中出现回路,那么就加入这条边
 * 
 * Kruskal算法的实现思路
 * 理论思想中边的排序和遍历都很好实现,难点就在于如何判断该边的加入会不会出现回路
 * 假设点a和b已经连通了,如果再在a和b之间连一条边那么势必就产生回路
 * 所以代码实现中只需要判断一条边的两个端点是否已经连通,是则不加入这条边,否则加入
 */
 
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10, M = 2e5 + 10, INF = 0x3f3f3f3f;

struct Edge {
    int a, b, w;
}edges[M];

int n, m;
int p[N];

int find(int u)
{
    if (p[u] != u) p[u] = find(p[u]);
    return p[u];
}
int kruskal()
{
    int res = 0, cnt = 0; // res:最小生成树的权重之和 cnt:最小生成树中边数,负责判断是否存在最小生成树
    
    // kruskal第一步:将所有边按照权值从小到大排序
    sort(edges, edges + m, [](Edge a, Edge b) {return a.w < b.w;});
    
    // kruskal第二步:遍历所有边,将符合条件的边加入集合中
    for (int i = 0; i < m; ++ i)
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        int pa = find(a), pb = find(b);
        
        if (pa != pb)
        {
            p[pa] = pb;
            res += w;
            ++ cnt;
        }
    }
    
    if (cnt == n - 1) return res; // n个点的最小生成树有n-1条边
    else return INF;
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; ++ i)
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w};
    }
    for (int i = 1; i <= n; ++ i) p[i] = i; // 并查集初始化
    
    int t = kruskal();
    
    if (t == INF) cout << "impossible" << endl;
    else cout << t << endl;
    
    return 0;
}

标签:int,最小,距离,生成,集合,INF,模板,dis
From: https://blog.51cto.com/u_14882565/7054033

相关文章

  • 设置随机数的生成起点
    代码结果展示......
  • emmet快速生成html标签和css样式
    emmet快速生成html标签语法1.生成标签,直接输入标签名,按下tab键即可;2.生成多个相同标签,加上即可,如生成3个div标签,div3;3.生成父子级的标签,使用>号,如ul>li;4.生成兄弟标签,使用+号,如div+p;5.生成带有类名或者id名的标签,直接写.demo或者#id按下tab键即可;6.如果生成的div类名是......
  • 代码随想录算法训练营第十六天| 104.二叉树的最大深度 111.二叉树的最小深度 222.
      104.二叉树的最大深度 (优先掌握递归)    卡哥建议:什么是深度,什么是高度,如何求深度,如何求高度,这里有关系到二叉树的遍历方式。大家要先看视频讲解,就知道以上我说的内容了,很多录友刷过这道题,但理解的还不够。   题目链接/文章讲解/视频讲解:https://programmerc......
  • 视频生成缩略图或pdf文件生成缩略图
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Web;usingSystem.Web.Mvc;usingMicrosoft.WindowsAPICodePack.Shell;usingSystem.Drawing;//usingPdfiumViewer;usingGhostscriptSharp;//usingSystem.IO;//usingiTextSharp;usingSyst......
  • 6529: 构造完全图 最小生成树
    描述 对于完全图G,若有且仅有一棵最小生成树为T,则称完全图G是树T的扩展出的。给你一棵树T,找出T能扩展出的边权和最小的完全图G。 输入 第一行N表示树T的点数。接下来N-1行:Si,Ti,Di;描述一条边(Si,Ti)权值为Di。保证输入数据构成一棵树。对于20%的数据,N<=10对于50%的......
  • C++欧几里得算法求最大公约数和最小公倍数
    定义最大公约数即为GreatestCommonDivisor,常缩写为gcd。一组整数的公约数,是指同时是这组数中每一个数的约数的数。一组整数的最大公约数,是指所有公约数里面最大的一个。那么如何求最大公约数呢?我们先考虑两个数的情况。欧几里得算法过程如果我们已知两个数\(a\)和\(......
  • 开启想象翅膀:轻松实现文本生成模型的创作应用,支持LLaMA、ChatGLM、UDA、GPT2等模型,开
    开启想象翅膀:轻松实现文本生成模型的创作应用,支持LLaMA、ChatGLM、UDA、GPT2等模型,开箱即用1.介绍TextGen实现了多种文本生成模型,包括:LLaMA、ChatGLM、UDA、GPT2、Seq2Seq、BART、T5、SongNet等模型,开箱即用。1.1最新更新[2023/06/15]v1.0.0版本:新增ChatGLM/LLaMA/Bloom模......
  • 代码生成以及数据生成
    我们在正常开发中设计到数据库的设计,以及对应实体类的代码。我现在讲解两个知识点。代码先行以及数据库先行1、代码先行就是你在程序中创建一个类库,专门用来管理你的实体类实体类写完后,利用ORM框架,譬如EF或者SqlSugar自带的性质可以直接生成数据库,以及数据表而代码实体类创......
  • 关于dev c++显示中文不显示,乱码和生成的可执行文件中文乱码
    1.不显示中文工具----编译器选项----显示-----去掉底下的复选框(第一个consolas下面)2,运行窗口中文乱码方法:1、工具—编译选项2、在第一个框中填入-fexec-charset=gbk3、勾选“编译器加入以下命令”4、重新编译一次以后运行。  ......
  • 小程序生成App:可跨平台开发的移动应用开发框架
    小程序生成App可以成为一种轻量低门槛的开发App的方式,但是需要根据具体情况进行选择。如果应用需要处理大量数据或需要进行复杂计算,或者需要实现原生特有的功能或交互效果,可能需要选择其他开发方式。在文章开始之前,我们看看目前市面上比较容易上手、低门槛开发App的框架和方式Rea......