最小生成树算法

最小生成树算法

文章目录

一、前言

今天介绍最小生成树算法,最小生成树算法前提:(对应数据结构要掌握的)

  • Kruskal算法
  • Prim算法

二、定义

生成树:仅针对于连通图,对于有n个顶点的连通图,至少有n-1条边,那么连接所有顶点的极小连通子图就是生成树

连通图:如果从任意一个顶点出发,沿着边总能走到任何其他顶点

非连通图不存在生成树,而是只存在生成森林

注:若在图的生成树上任意加上一条边,就必然形成回路

最小生成树 :对于连通网来说,边是带权值的,生成树的各边也带权值,因此把生成树的各边的权值总和称为生成树的权,把权值最小的生成树称为最小生成树

三、应用

  • 铺设道路

  • 建立通讯线路等

    n个点,连边,形成的权值最小

四、Kruskal算法

4.1 算法思想

从小到大加入边 ---- 贪心算法

加边的同时,需通过并查集 判断该边的两个端点是否属于同一棵树,避免形成环。

4.2 步骤

  • 把图上的每一条边存在一个边集数组里,数组的每个元素应有(起点,终点,边权)三个数据
  • 将该边数组按边权从小到大排序
  • 依次按边的边权从小到大枚举每一条边,如果边的两个端点已经连通了,那就跳过这条边(通过并查集判断)
  • 否则把总答案累计上这条边
  • 用并查集merge这条边的两个端点
  • 返回第3步(循环这个过程)

4.3 时间复杂度

因为要遍历整个图,时间复杂度是O(n + e),再加上排序(O(eloge)),因此算法总体取决于排序的时间复杂度(冒泡: e 2 e^2 e2;选择: e 2 e^2 e2) e l o g e eloge eloge(快排)

4.4 代码

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=110;		// 最大顶点数
const int maxm=10010;	// 最大边数
struct Edge				// 使用结构体存储每一条边,便于排序
{
    int u, v, w;	// 表示有一条(u,v)的无向边,边权为w
    // 注意:这里的存边和链式前向星的存边不一样(单纯来存终点,起点,下一条边的编号)
} e[maxm];
int ecnt;		// 用于边表计数
void addEdge(int u, int v, int w)	// 加入一条无向边
{
    ++ecnt;
    e[ecnt].u = u;
    e[ecnt].v = v;
    e[ecnt].w = w;
}
int fa[maxn];	// 并查集相关----father
int find(int x)
{
    return x == fa[x] ? x : fa[x] = find(fa[x]);	// 路径压缩
}
int n,m;		// 顶点数 边数
bool cmp(const Edge &a, const Edge &b)		// 根据边权w进行排序
{
    return a.w < b.w;
}
int Kruskal()	// Kruskal 算法核心过程
{
    for(int i = 1; i <= n; i++)
    {
        fa[i] = i;	// 初始化并查集
    }
    sort(e + 1, e + ecnt + 1, cmp);		// 改写sort函数,只比较权值
    int sum = 0;						// 边权和
    for(int i = 1; i <= ecnt; i++)		// 枚举每条边
    {
        int u = e[i].u;
        int v = e[i].v;
        u = find(u);
        v = find(v);
        if(u != v)
        {
            fa[u] = v;				// fa[v] = u;(都行,让一个点作另一个点的父亲)
            sum += e[i].w;
        }
    }
    return sum;
}
int main()
{
    scanf("%d %d", &n, &m);			// 有向图(只有1个add边)
    int x, y, w;
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d %d", &x, &y, &w);
        addEdge(x, y, w);			// 加到边集数组里
    }
    int ans = Kruskal();
    printf("%d\n", ans);
    
    return 0;
}

五、Prim算法

5.1 算法思想

同样基于贪心

5.2 定义

从一个节点开始,不断加点Kruskal是加边)。每次总是选出一个离生成树距离最小 的点去加入生成树,最后实现最小生成树。类似Dijkstra算法

5.3 步骤

  • 将图的顶点分成2个集合,集合A = {NULL},用于存放最小生成树中的顶点。集合B = {all node},存放还未加入到生成树中的顶点。
  • 在B中任选一顶点充当起始顶点,加入到集合A中,并更新A到B各顶点的距离dist[N]。
  • 选择距离最近的一个顶点,加入到集合A中,并更新A到B各顶点的距离dist[N]...直到所有的顶点都加入到A中。

5.4 时间复杂度

O( v 2 v^2 v2​​)

放v个顶点,循环v次,找最短距离,又要循环v次

5.5 代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5005;
const int inf = 0x7fffffff;
int cnt;
struct edge						// 链式前向星的边集数组
{
    int u, w, next;
} e[2 * maxn];
int head[maxn];					// 存储每个顶点的第一条边的编号
int dis[maxn];					// 维护每一点到最小生成树的距离
bool vis[maxn];					// 标记点是否被加入到最小生成树中
int n, m;
void add(int x, int y, int w)		// 链式前向星的建边方法
{
    cnt++;						// 给这条边定一个编号
    e[cnt].u = y;				// 这条边从x出发指向y
    e[cnt].w = w;				// 边权为w
    e[cnt].next = head[x];		// 把x之前的第一条边记成这条边的下一条边
    head[x] = cnt;				// 现在x的第一条边就是刚加的这条
}
int prim()
{
    for(int i = 1; i <= n; i++)
    {
        dis[i] = inf;				// dis数组是每一个点到最小生成树的距离
    }
    dis[1] = 0;					// 出发点(看题目是否规定)
    vis[1] = 1;
    int now = 1;
    for(int i = head[now]; i; i = e[i].next)	// 链式前向星的遍历方法
    {
        int u = e[i].u;			// 出边
        dis[u] = min(dis[u], e[i].w);
    }
    int tot = 0;
    int sum = 0;					// 权值和
    while(tot < n - 1)				// 循环n - 1轮
    {
        int mindis = inf;
        for(int i = 1; i <= n; i++)
        {
            if(!vis[i] && dis[i] < mindis)
            {
                now = i;
                mindis = dis[i];
            }
        }
        if(mindis == inf)			// 可能是非连通图
        {
            return -1;
        }
        tot++;
        sum += mindis;
        vis[now] = 1;
        for(int i = head[now]; i; i = e[i].next)// 链式前向星的遍历方法
        {
            int u = e[i].u;
            if(vis[u])
            {
                continue;
            }
            dis[u] = min(dis[u], e[i].w);
        }
    }
    return sum;
}
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);		// 无向图
        add(x, y, z);
        add(y, x, z);
    }
    int ans = prim();
    if(ans == -1)
    {
        printf("orz");
    }
    else
    {
        printf("%d", ans);
    }
    return 0;
}

加边方法:

  • u, v, w或者x, y, w就是kruskal算法的加边方式
  • u, w, next就是dijkstra算法的加边方式

六、解题

  • 稠密图(边多)----prim算法

    eloge就会超时,因此最好使用prim算法

  • 边和点数目差不多----Kruskal算法

七、题目

7.1 洛谷

  • P1194 买礼物
  • P1265 公路修建

八、小结

根据不同的题目,进行选择合适的算法进行求解

相关推荐
2401_892070982 天前
【Linux C++ 日志系统实战】LogFile 日志文件管理核心:滚动策略、线程安全与方法全解析
linux·c++·日志系统·日志滚动
yuzhuanhei2 天前
Visual Studio 配置C++opencv
c++·学习·visual studio
Wenweno0o2 天前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
小O的算法实验室2 天前
2026年ASOC,基于深度强化学习的无人机三维复杂环境分层自适应导航规划方法,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进
‎ദ്ദിᵔ.˛.ᵔ₎2 天前
LIST 的相关知识
数据结构·list
chenjingming6662 天前
jmeter线程组设置以及串行和并行设置
java·开发语言·jmeter
cch89182 天前
Python主流框架全解析
开发语言·python
不爱吃炸鸡柳2 天前
C++ STL list 超详细解析:从接口使用到模拟实现
开发语言·c++·list
M--Y2 天前
Redis常用数据类型
数据结构·数据库·redis
十五年专注C++开发2 天前
RTTR: 一款MIT 协议开源的 C++ 运行时反射库
开发语言·c++·反射