最小生成树算法

最小生成树算法

文章目录

一、前言

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

  • 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 公路修建

八、小结

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

相关推荐
虚幻如影1 小时前
python识别验证码
开发语言·python
ChineHe1 小时前
基础篇003_Python基础语法
开发语言·人工智能·python
Klong.k2 小时前
判断是不是素数题目
数据结构·算法
QQsuccess2 小时前
AI全体系保姆级详讲——第一部分:了解AI基本定义
人工智能·算法
NX-二次开发2 小时前
UG CAM API 获取、设置切削层中的切削方式类型方法,如设置仅底面、恒定、临界深度的类型
c++
_日拱一卒2 小时前
LeetCode:移动零
算法·leetcode·职场和发展
沉沙丶2 小时前
关于matlab分析电流THD的一些探究和记录
开发语言·matlab·电机控制·foc·永磁同步电机·模型预测·预测控制
chase。2 小时前
Python包构建工具完全指南:python -m build 使用详解
开发语言·chrome·python
SuperEugene2 小时前
前端 utils 工具函数规范:拆分 / 命名 / 复用全指南,避开全局污染等高频坑|编码语法规范篇
开发语言·前端·javascript