【数据结构】图论——Prim算法和Kruskal算法

目录

  • Prim算法和Kruskal算法
    • Prim算法的原理
      • 数据结构
      • 算法步骤解释
      • 算法实现
      • 代码示例
    • Kruskal 算法
      • Kruskal算法的原理和步骤
      • Kruskal算法的实现
        • 数据结构
        • 并查集操作
        • Kruskal算法

Prim算法和Kruskal算法

文章: 【数据结构】图论(图的储存方式,图的遍历算法DFS和BFS、图的遍历算法的应用、图的连通性问题)

Prim算法的原理

  1. 初始化:从图中的一个起始顶点开始,逐步将权值最小的边添加到生成树中。
  2. 扩展生成树:在每一步中,选择当前生成树中所有顶点到生成树外所有顶点中权值最小的一条边,并将该边和对应的顶点加入生成树。
  3. 重复上述步骤:直到所有顶点都包含在生成树中。

数据结构

图采用邻接矩阵邻接表存放均可。下面以邻接矩阵为例实现Prim算法:

  • 图的表示:用一个二维数组arcs表示图的邻接矩阵,arcs[i][j]表示顶点i到顶点j之间的边的权值。vexnum表示图的顶点数,arcnum表示边的数量。
  • 边的类型EdgeType结构体用于存放每个顶点到生成树中顶点的最小权值边的信息。adjvex表示这条边连接的另一个顶点,lowcost表示这条边的权值。
#define MAX 100
#define MAXEDGE 1000000

typedef struct {
    int arcs[MAX][MAX];
    int vexnum, arcnum;
} AGraphs;

typedef struct {
    int adjvex; // 与生成树中的点连接的最好情况,记录的是与生成树中的哪个点(记录的是点的位置)
    int lowcost; // 记录的是该最好情况的弧的权重
} EdgeType;

EdgeType closedge[MAX]; // 采用一维数组closedge[MAX]存放图中每个顶点与生成树中顶点相连的最好情况

算法步骤解释

  1. 初始化closedge数组
void prim(AGraphs G, int u) {
    int i, j, k;
    EdgeType closedge[MAX];

    for (j = 0; j < G.vexnum; j++) {
        closedge[j].adjvex = u; // 此次选中的与生成树中连接情况最好的点的位置
        closedge[j].lowcost = G.arcs[u][j]; // 记录下来这个数值的大小
    }

    closedge[u].lowcost = 0; // 如果顶点已经包含在生成树中,则lowcost设为0。
  1. 逐步扩展生成树
    for (i = 1; i < G.vexnum; i++) {
        k = minclosedge(closedge);
        printf("(%d,%d)\n", closedge[k].adjvex, k);
        closedge[k].lowcost = 0;

        for (j = 0; j < G.vexnum; j++) {
            if (G.arcs[k][j] < closedge[j].lowcost) {
                closedge[j].lowcost = G.arcs[k][j];//选中
                closedge[j].adjvex = k; //选中
            }
        }
    }
}
  1. 找最小权值边
int minclosedge(EdgeType closedge[]) {
    int min = MAXEDGE, j, k = -1;
    for (j = 0; j < G.vexnum; j++) {
        if (closedge[j].lowcost != 0 && closedge[j].lowcost < min) {
            min = closedge[j].lowcost; //选中
            k = j;
        }
    }
    return k;
}

算法实现

每一步只保留不在生成树中的点和生成树相连的最好情况

实现算法时,在每一步我们只保留不在生成树中的点生成树相连的最好情况,而不是考察不在生成树中的点和生成树相连的所有情况。
每次加入一个顶点和一条边进入生成树后,我们都考察一下不在生成树中的点和生成树中的点相连的最好情况是否被新加入的点更新。

代码示例

#define MAX 100
#define MAXEDGE 1000000

typedef struct {
    int arcs[MAX][MAX];
    int vexnum, arcnum;
} AGraphs;

typedef struct {
    int adjvex;
    int lowcost;
} EdgeType;

// 采用一维数组closedge[MAX]存放图中每个顶点与生成树中顶点相连的最好情况

/*
当顶点v尚未加入生成树时,closedge[v]存放的是v与生成树中的顶点相连的最好情况:
v与生成树中的顶点的所有连边中权值最小的那条边;
closedge[v].adjvex存放的这条权值最小的边的另一个顶点,
closedge[v].lowcost存放的这条权值最小的边的权值。

如何区分生成树中的顶点和不在生成树中的顶点呢?

closedge[w].lowcost==0表示w已经加入生成树
*/

void prim(AGraphs G, int u) {
    int i, j, k;
    EdgeType closedge[MAX];

    for (j = 0; j < G.vexnum; j++) {
        closedge[j].adjvex = u; // 此次选中的与生成树中连接情况最好的点的位置
        closedge[j].lowcost = G.arcs[u][j]; // 记录下来这个数值的大小
    }

    closedge[u].lowcost = 0; // 如果顶点已经包含在生成树中,则lowcost设为0。

    for (i = 1; i < G.vexnum; i++) {
        k = minclosedge(closedge);
        printf("(%d,%d)\n", closedge[k].adjvex, k);
        closedge[k].lowcost = 0;

        for (j = 0; j < G.vexnum; j++) {
            if (G.arcs[k][j] < closedge[j].lowcost) {
                closedge[j].lowcost = G.arcs[k][j];
                closedge[j].adjvex = k;
            }
        }
    }
}

int minclosedge(EdgeType closedge[]) {
    int min = MAXEDGE, j, k = -1;
    for (j = 0; j < G.vexnum; j++) {
        if (closedge[j].lowcost != 0 && closedge[j].lowcost < min) {
            min = closedge[j].lowcost;
            k = j;
        }
    }
    return k;
}

// 时间复杂度:O(n^2) Prim算法适合于稠密图

画图求解:

在这里插入图片描述
时间复杂度: O ( n 2 ) O(n^2) O(n2)
Prim算法适合于稠密图

问法
最小生成树的求解过程
文字叙述 画表 求解过程
伪码描述(上述程序)

Kruskal 算法

先排序,对所有边按照权值升序排序
从小开始加,只要不产生回路,最后加到 n − 1 n-1 n1
需要 排序(适合于吸收图)

Kruscal算法适合稀疏图,时间复杂度为O(eloge),e为图的边数,因为该算法要对边按照权值排序,(堆、快速。归并)排序算法的平均时间复杂度O(eloge)

Kruskal算法是一种用于查找加权无向图的最小生成树(MST)的贪心算法。它通过逐步选择权值最小的边并确保不会形成环来构建最小生成树。下面详细描述Kruskal算法的实现过程:

Kruskal算法的原理和步骤

  1. 初始化

    • 创建一个包含所有图中顶点的集合,每个顶点初始时在不同的集合中。
    • 初始化最小生成树为空。
  2. 排序

    • 将图中的所有边按权值从小到大排序。
  3. 逐步构建生成树

    • 依次检查排序后的每一条边,如果该边连接的两个顶点位于不同的集合中,则将这条边加入最小生成树,并合并这两个顶点所在的集合。
    • 如果该边连接的两个顶点已经在同一集合中,则跳过这条边,以避免形成环。
  4. 终止条件

    • 当最小生成树包含的边数等于图中顶点数减一时,算法终止。

Kruskal算法的实现

为了实现Kruskal算法,需要使用**并查集(Disjoint Set Union, DSU)**数据结构来管理和合并顶点集合,并检查是否会形成环。

以下是Kruskal算法的详细实现步骤和代码示例:

数据结构
#define MAX 100
#define MAXEDGE 1000000

typedef struct {
    int u, v;  // 边的两个顶点
    int weight;  // 边的权值
} Edge;

typedef struct {
    Edge edges[MAXEDGE];  // 图中的所有边
    int vexnum, edgenum;  // 顶点数和边数
} Graph;

int parent[MAX];  // 并查集数组
int rank[MAX];    // 并查集的秩数组(用于路径压缩优化)
并查集操作
// 查找操作,带路径压缩
int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]);
    }
    return parent[x];
}

// 合并操作,带按秩合并
void unionSet(int x, int y) {
    int rootX = find(x);
    int rootY = find(y);

    if (rootX != rootY) {
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }
}
Kruskal算法
// 边的比较函数,用于排序
int cmp(const void* a, const void* b) {
    Edge* edgeA = (Edge*)a;
    Edge* edgeB = (Edge*)b;
    return edgeA->weight - edgeB->weight;
}

// Kruskal算法
void kruskal(Graph G) {
    int i;
    Edge result[MAX];  // 用于存储最小生成树中的边
    int e = 0;  // 最小生成树中的边数

    // 初始化并查集
    for (i = 0; i < G.vexnum; i++) {
        parent[i] = i;
        rank[i] = 0;
    }

    // 按边的权值排序
    qsort(G.edges, G.edgenum, sizeof(Edge), cmp);

    for (i = 0; i < G.edgenum; i++) {
        Edge nextEdge = G.edges[i];

        int x = find(nextEdge.u);
        int y = find(nextEdge.v);

        // 如果这条边不会形成环
        if (x != y) {
            result[e++] = nextEdge;  // 将边加入结果中
            unionSet(x, y);  // 合并两个顶点的集合
        }
    }

    // 打印最小生成树
    printf("Following are the edges in the constructed MST:\n");
    for (i = 0; i < e; i++) {
        printf("%d -- %d == %d\n", result[i].u, result[i].v, result[i].weight);
    }
}
  1. 数据结构:定义了图结构体和边结构体,用于存储图中的所有边。并查集用于管理顶点集合。
  2. 并查集操作:定义了并查集的查找和合并操作,用于判断是否会形成环。
  3. 排序:对所有边按权值进行排序。
  4. 构建最小生成树:依次检查每条边,使用并查集判断是否会形成环。如果不会,则将边加入最小生成树,并合并顶点集合。
  5. 输出结果:打印最小生成树中的所有边。

Kruskal算法通过边的排序和并查集的使用,实现了高效的最小生成树构建过程。该算法适用于稀疏图,因为它主要操作的是边而不是顶点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/676687.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ai绘画工具Stable Diffusion,手把手教你训练你的专属Lora模型,神级教程建议收藏!

哈喽&#xff0c;大家好&#xff0c;我是设计师阿威。 今天给大家带来的是Stable Diffusion训练Lora的教程&#xff0c;希望对大家有帮助。 一、硬件要求 我们知道Stable Diffusion WebUI对显卡要求比较高&#xff0c;同样Lora训练对显卡要求更高&#xff0c;所以要想训练一…

芝麻IP好用吗?来测试了!

作为老牌代理IP服务厂商&#xff0c;芝麻IP和青果网络代理IP都做的不错&#xff0c;市场上几乎可以是有口皆碑了&#xff0c;上次测试了青果网络的代理IP&#xff0c;效果表现得还挺不错&#xff0c;和他们自己宣传的以及客户对他们的评价大差不差。 总的来说&#xff0c;他们家…

Marin说PCB之Max parallel知多少?

今天是个阳光明媚&#xff0c;万里乌云的好日子。小编我一如既往地到家打开电脑准备看腾讯视频的五十公里桃花坞的第四季&#xff0c;在看到汪苏泷汪台说650电台要解散的时候小编我差点也哭了。650电台之于桃花坞就像乐队的鼓手一样&#xff0c;都是一个团队的灵感啊&#xff0…

视频号电商再升级,誓要分走抖音的蛋糕

2022年&#xff0c;马化腾对视频号的评价是&#xff1a;“微信最亮眼的业务就是视频号&#xff0c;基本上是全场的希望。”到了2024年&#xff0c;这个评价变成了&#xff1a;“视频号经过一年多的发展&#xff0c;的确不负众望。” 一年多的时间&#xff0c;从全村的希望&…

Docker 基础使用(3) 存储卷

文章目录 存储卷的含义存储卷的分类存储卷的作用存储卷的使用存储卷实际使用案例 ---- MySQL灾难恢复存储卷的局限 Docker 基础使用&#xff08;0&#xff09;基础认识 Docker 基础使用 (1) 使用流程概览 Docker 基础使用&#xff08;2&#xff09; 镜像与容器 Docker 基础使用…

MetaGPT:重塑自然语言编程,多智能体引领模型训练的革新探索

近年来&#xff0c;人工智能&#xff08;AI&#xff09;和自然语言处理&#xff08;NLP&#xff09;领域取得了重大进展&#xff0c;MetaGPT作为一个多智能体框架&#xff0c;正引领着这一领域的变革。本文将深入探讨MetaGPT的核心技术、实际应用及其对未来编程模式的影响。 引…

Python保存为json中文Unicode乱码解决json.dump()

保存为json中文Unicode乱码&#xff1a; 可以看到&#xff0c;中文字符没有乱码&#xff0c;只是出现了反斜杠&#xff0c;此时解决方法应考虑是否进行了二次序列化。 一、原因1 在dump时加入ensure_asciiFalse 即可解决&#xff0c;即json.dump(json_data, f, indent4, en…

antd-vue - - - - - a-select结合i18n使用(踩坑问题)

antd-vue - - - - - a-select结合i18n使用&#xff08;踩坑问题&#xff09; 1. 当前代码 & 效果2. 解决办法 1. 当前代码 & 效果 <a-selectv-model:value"formState.quickSwitching":options"quickSwitchingOptions"search"handleSearch…

Linux.用户

使用su - 切换用户 切换root时要输入密码&#xff0c;但是看不到 创建用户组 groupadd用户组名&#xff0c;用getent查看有哪些组 getent group 创建用户 在root身份中使用gentent passwd 可以查当前的用户信息 使用getent group查看有哪些组 使用chmod修改权限 快捷方法…

S4 BP 维护

前台输入Tcode:BP 问候填写金税开票信息使用的开户行名称,注释填写金税开票信息使用的开户行代码 屏幕下滑按需填写其他数据,如:街道2,街道3,街道/门牌号,街道4,街道5,区域,邮编、城市、国家、地区、语言,电话(发票地址里的电话(必须是客户开票资料里提供的电话,会…

k8s部署(单点或)高可用consul集群

在 Kubernetes 集群上部署一个高可用的 Consul 集群&#xff0c;确保一个节点挂了之后不会影响已注册到 Consul 的服务。利用 StatefulSet 和无头服务 HeadLess 的选举机制来实现 Consul 集群的高可用性&#xff0c;数据持久化方式选择HostPath&#xff0c;通过 nodeSelector 节…

HTML基本元素包含HTML表单验证

可将以下代码复制另存为一个HTML文件浏览器打开自己去看看实际使用效果 <!DOCTYPE html> <html> <head> <meta charset"utf-8"><title>测试</title> </head> <body> <h1>很多事</h1> <h1><b&…

视频融合共享平台LntonCVS视频监控业务平台可视化智慧仓储应用方案

对于当前许多大型工厂和物流基地来说&#xff0c;仓库是存放物品的重要场所。仓储存放着大量货物&#xff0c;并且配备有大量辅助设备&#xff0c;因此需要全方位的监管以避免发生安全事故&#xff0c;造成财产损失。传统的人工巡检方式已经无法满足现有大规模监管的要求&#…

【第九课】空间数据基础与处理——空间参考处理

一、前言 地图图层中的所有元素都具有特定的地理位置和范围&#xff0c;这使得它们能够定 位到地球表面上相应的位置。精确定位地理要素对于制图和 GIS来说都至关 重要&#xff0c;而要正确地描述要素的位置和形状&#xff0c;需要引入一个用于定义位置的框 架———空间参考。…

从MLP到卷积

1.从MLP到卷积层 最近要做多通道的实验&#xff0c;所以重新将处理图像的基础模型回顾一下&#xff0c;什么是卷积&#xff1f;卷积本质是是一种特殊的全连接层。 1.1怎么w的权重从一个值变成了4维呢?可以这样理解&#xff0c;在此举一个例子&#xff1a; 其实本质可以看成&…

uniapp3步使用goeasy完成本地消息推送

1.注册登录goeasy&#xff0c;下载测试demo 2.替换demo中main.js中的key 3.打包一个H5&#xff0c;一个自定义基座。 h5发消息&#xff0c;app收消息&#xff0c;然后创建消息通知就好了。记得打开app的消息通知 demo很简单&#xff0c;demo都跑通了&#xff0c;在搬到自己项目…

NEJM新英格兰医学期刊文献在家如何查阅下载

今天收到的求助文献中有一篇是NEJM新英格兰医学期刊中的一篇文献&#xff0c;篇名“Osimertinib after Chemoradiotherapy in Stage III EGFR -Mutated NSCLC” 首先我们先简单了解一下NEJM新英格兰医学期刊&#xff1a; NEJM新英格兰医学期刊&#xff1a;New England Journa…

c# - - - winform 右下角气球提示通知

c# - - - winform 右下角气球提示通知 winform 右下角气球提示通知 1.1 winform 右下角气球提示通知 在工具箱中点击 NotifyIcon 控件&#xff0c;拖动到 Form1 窗体上添加这个控件。 在“提示”按钮的点击事件中写气球提示通知内容。 public partial class Form1 : Form {…

如何利用CXL技术突破内存墙?-2

为了解决这些问题&#xff0c;业界正积极寻求新的技术和标准&#xff0c;比如Compute Express Link (CXL)&#xff0c;它旨在通过提供标准化的高速互连来提高内存带宽、降低延迟&#xff0c;并简化内存扩展的软件集成&#xff0c;从而有效地打破内存墙的限制。 通过使用CXL&am…

i.MX8MP平台开发分享(RDC资源分配控制器篇)

1.spec RDC 配置信息被发送到结构端口、内存垫片、信号控制器和外设&#xff0c;以根据域分配控制访问。 结构使用与每个端口相关的域标识符&#xff0c;将此信息与总线事务一起包含在内。当从属加密垫圈遇到总线事务时&#xff0c;它会将事务域 ID 与 RDC 提供的允许域列表进…