算法学习模板——最小生成树问题

这篇博客详细介绍了图论中的最小生成树问题,包括生成树的基本概念、Kruscal算法和Prim算法的实现,以及这两种算法的联系。此外,还提到了生成树的衍生问题,如增量最小生成树和最小瓶颈树。博客内容丰富,适合算法学习者参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        万里之行,始于足下。本博客总结暑期学习到的图论模板第三部分,以便于日后查询使用。作者水平有限,难免存在疏漏不足,恳请诸位看官斧正。倘若我的文章可以帮到你,十分荣幸。感谢19级学长lsw提供的灵感。本篇博客主要讲生成树相关问题。

更新日志:

       1.于2021.9.23对Kruscal算法代码部分进行了可读性的优化。

目录

1.关于生成树的前驱知识

      1.何谓“生成树”?

       2.补充:树的一些等价定义

       3.最小生成树

2.Kruscal算法

3.Prim算法

4.Prim算法和Kruscal算法的联系

5.生成树的衍生问题

1.增量最小生成树

2.最小瓶颈树


1.关于生成树的前驱知识

      1.何谓“生成树”?

       关于生成树我们要把“生成分开来理解。生成即为生成子图定义:若图G的一个子图G’包含G的所有顶点,则称G’为G的一个生成子图。当然,G’的边集是G的子集了。在图论中,树就是n个结点所能形成的最小的连通图,一共有n-1条边。而生成树就是指图G的一个为树的生成子图。

       2.补充:树的一些等价定义

        •无向无环的连通图

        •任意两个结点之间有且仅有一条简单路径的无向图

        •任何边均为桥的连通图

        没有圈,且在任意不同两点间添加一条边之后所得图含唯一的一个圈的图

       3.最小生成树

        在无向连通图G中我们找到一个生成树T,使得T权值和最小的生成树,那么我们称TMST,即最小生成树。接下来着重介绍MST的两种求法。

2.Kruscal算法

      一开始认定每一个点都为独立的集合。按照边权大小检查每一条边,找到代价最小的边,如果该边连接连个不同的集合,将边加入MST,合并两个集合。该算法的时间复杂度为O(eloge)(e为网中的边数)。如何合并集合?这里就需要我们之前讲过的并查集相关的知识了。

并查集相关内容传送门:C++算法学习模板收集——图论篇(1)

上例题:P3366 【模板】最小生成树 

#include <bits/stdc++.h>
#define re register int//宏定义,设置加速循环
using namespace std;

const int maxm=2e5+7,maxn=5007;
int n,m,f[maxn],sum=0,ans=0;

struct Edge
{
    int from,to,w;
}edge[maxm*2];

bool cmp(const Edge &a,const Edge &b)
{
    return a.w<b.w;
}//cmp函数用于排序边的权值

int fi(int n)
{
    if(f[n]!=n)
    {
        return fi(f[n]);
    }
    return n;
}

inline void mer(int i,int j)
{
    f[fi(i)]=fi(j);
}

void init()
{
    for(int i=1;i<=n;i++)
    {
        f[i]=i;
    }
}//以上为并查集相关内容

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(re i=1;i<=m;i++)
    {
        cin>>edge[i].from>>edge[i].to>>edge[i].w;
    }//存图
    init();
    sort(edge+1,edge+1+m,cmp);//边的排序
    for(re i=1;i<=m;i++)//边的代价从小到大找喽
    {
        if(fi(edge[i].to)!=fi(edge[i].from))//合并操作
        {
            mer(edge[i].to,edge[i].from);
            sum++;//计数
            ans+=edge[i].w;
            if(sum==n-1)
            {
                cout<<ans;
                return 0;
            }//MST有n-1条边
        }
    }
    cout<<"orz";
    return 0;
}

3.Prim算法

       Prim算法的核心思维算法是贪心。选择任意点为根,找不在树上的离树最近的点加入生成树。其优化的时间复杂度为O(nlongm)该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(Robert C. Prim)独立发现;1959年, 艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合, 普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

       该算法 与我们之前讲过的 Dijkstra算法相似,同样的,它也可以采用优先队列优化。                 Dijkstra算法相关传送门:C++算法学习模板收集——图论篇(2)

            例题:P3366 【模板】最小生成树 没错,虽然不是同一算法,但是是同一地点

#include <bits/stdc++.h>
#define re register int
using namespace std;

typedef pair <int,int> p;

const int maxn=5005,maxm=2e5+7;
int n,m,cnt,ans,sum;
int dis[maxn],head[maxn],vis[maxn];
priority_queue<p,vector<p>,greater<p> > Q;//还是辣个优先队列优化

struct edge
{
	int v,w,next;
}e[maxm*2];//结构体存边

inline void addedge(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}//链式前向星存图

void prim()
{
    dis[1]=0;
	Q.push(make_pair(0,1));//初始化
    while(!Q.empty()&&sum<n)
    {
        int d=Q.top().first,u=Q.top().second;
        Q.pop();
        if(vis[u])
        {
            continue;
        }
        sum++;//计数
        ans+=d;
        vis[u]=1;//将点及其连通的加入MST中
        for(re i=head[u];i!=-1;i=e[i].next)//链式前向星的遍历
        {
            if(e[i].w<dis[e[i].v])
            {
                dis[e[i].v]=e[i].w;
                Q.push(make_pair(dis[e[i].v],e[i].v));
            }//更新最小边权并将其压入优先队列
        }
    }
}//该算法实现可以对照Dijkstra算法

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    memset(dis,114514,sizeof(dis));
    memset(head,-1,sizeof(head));//你是一个,一个一个一个初始化
    cin>>n>>m;
    for(re i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        addedge(u,v,w);
        addedge(v,u,w);
    }//存图
    prim();//执行算法
    if(sum==n)//n个点都在MST里了
    {
        cout<<ans;
    }
    else
    {
        cout<<"orz";
    }
    return 0;
}

4.Prim算法和Kruscal算法的联系

       与Kruscal算法不断加边不同,Prim算法的关注点是加点需要注意一点,如果使用优先队列优化的Prim时间复杂度并不会优于Kruscal。原理是堆的实现方式不同。因为Prim算法是找点,只有暴力实现的Prim算法会在稠密图上可能优于Kruscal算法。

5.生成树的衍生问题

                                     这一部分我还在学习,挖个坑先,争取早日来填

1.增量最小生成树

       从包含n个点的空图开始,依次加入m条带权边。每加入一条边。输出当前图中MST权值。

2.最小瓶颈树

       无向图G的瓶颈生成树是这样的一个生成树,它的最大的边权值在G的所有生成树中最小。何解,二分?最小生成树?亦或最短路?

       下节预告:下一次图论博客将介绍常见的图存储方法。包括邻接矩阵链式前向星和邻接表

       “山再高,往上攀,总能登顶;路再长,走下去,定能到达。”路漫漫其修远兮,吾将上下而求索,共勉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值