数据结构应用实例(四)——最小生成树
Content:
- 一、问题描述
- 二、算法思想
- 三、代码实现
- 四、两种算法的比较
- 五、小结
一、问题描述
利用 prim 算法和 kruskal 算法实现最小生成树问题;
二、算法思想
首先判断图是否连通,只有在连通的情况下才进行最小树的生成;
三、代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxx 999999
#pragma warning(disable:4996)typedef struct arc//弧
{ int index;//指向节点编号float weight;//边的权值struct arc *next;
}AR;typedef struct edge
{int i,j;//边的起点和终点编号float edge;//边的权值
}EG;typedef struct MyGraph
{int type;//0表示无向图,1表示有向图int arcnum,vexnum;//边的个数、顶点个数char **vexname;//存放顶点名称的二维数组 AR *N;//表头数组float **A;//邻接矩阵,这里使用float型EG *E;//存放边的三元组
}GH;int findvex(char *s,GH *G);//找到图G中名称为s的节点编号,并将其返回
void creatGraph(GH *G);//创建图G
void showGraph(GH *G);//以邻接表的形式显示图Gint isconnect(GH *G);//判断图G是否连通,是则返回1,否则返回0
int BFSvisit(int start,GH *G);//对图G从start号点开始广度优先遍历,返回访问过的节点个数void prim(GH *G,int start);//利用prim算法,求图G中以start为起点的最小生成树
void showTree(AR *N,char **vexname,int n);//显示最小生成树,N表示表头数组,vexname中存放顶点名称,n表示节点数量void kruskal(GH *G);//利用kruskal算法,求图G中的最小生成树
void heapsort(EG *E, int n);//对含有n条边的三元组E进行堆排序
void adjust(int index, EG *E, int n);//对利用顺序表E表示的含有n个节点的完全二叉树的index号节点进行筛选
void changeflag(int m,int y,int *flag, AR *N,int n);//将图G中m号节点所在的连通分支上的点的flag统一赋值为y,N为表头数组,n表示原图中节点个数int main(void)
{GH *G;int begin;//起点编号//创建图G=(GH *)malloc(sizeof(GH));creatGraph(G);printf("图的邻接表形式:\n");showGraph(G);//if (!isconnect(G))//{// printf("该图不连通,不具有最小生成树.\n");
// exit(-1);
// }printf("该图连通,寻找最小生成树:\n");printf("\n\n====================prim算法========================\n");printf("请输入起点编号(-1结束):");scanf("%d", &begin);while (begin != -1){prim(G, begin);printf("请输入起点编号(-1结束):");scanf("%d", &begin);}printf("\n\n================kruskal算法========================\n");kruskal(G);free(G);return 0;
}int findvex(char *s,GH *G)//找到图G中名称为s的节点编号,并将其返回
{for(int i=0;i<G->vexnum;i++){if(strcmp(s,G->vexname[i])==0)return i;}printf("该点不存在\n");exit(-1);
}void creatGraph(GH *G)//创建图G
{int i,j,k,n,m;float edge;char filename[]="graph.txt";char str[10],str1[10];FILE *fp;AR *p;fp=fopen(filename,"r");if(!fp){printf("文件打开失败!\n");exit(-1);}//读取图的类型和节点数量fscanf(fp,"%d%d",&G->type,&n); G->vexnum=n;//分配空间G->vexname=(char **)malloc(n*sizeof(char *));G->N=(AR *)malloc(n*sizeof(AR));G->A=(float **)malloc(n*sizeof(float *));//初始化A和Nfor (i = 0; i < n; i++){G->N[i].next = NULL;G->A[i] = (float *)malloc(n*sizeof(float));for (j = 0; j < n; j++)G->A[i][j]=maxx;}//读取顶点名称for(i=0;i<n;i++){fscanf(fp,"%s",str);//先分配空间G->vexname[i]=(char *)malloc(strlen(str)*sizeof(char));strcpy(G->vexname[i],str);} //读取边fscanf(fp,"%d",&m);G->arcnum=m;//边的个数G->E=(EG *)malloc((m+1)*sizeof(EG));//0号位置不存放数据for(k=1;k<=m;k++){fscanf(fp,"%s%s%f",str,str1,&edge);i=findvex(str,G);j=findvex(str1,G);//邻接表中添加节点p=(AR *)malloc(sizeof(AR));p->index=j;p->weight=edge;p->next=G->N[i].next;G->N[i].next=p;//邻接矩阵G->A[i][j]=edge;//三元组G->E[k].i=i;G->E[k].j=j;G->E[k].edge=edge;if(G->type==0){//邻接表中添加节点p=(AR *)malloc(sizeof(AR));p->index=i;p->weight=edge;p->next=G->N[j].next;G->N[j].next=p;//邻接矩阵G->A[j][i]=edge;}}fclose(fp);
}void showGraph(GH *G)//以邻接表的形式显示图G
{int i;AR *p; //用于遍历for (i = 0; i < G->vexnum; i++){printf("%s",G->vexname[i]);p=G->N[i].next;while (p){if (G->type == 1)printf("-->");elseprintf("--");printf("%s(%.2f)",G->vexname[p->index],p->weight);p=p->next;}printf("\n");}printf("\n");
}int isconnect(GH *G)//判断图G是否连通,是则返回1,否则返回0
{int k;k=BFSvisit(0,G);//k:广度优先遍历访问到的节点个数if(k==G->vexnum)return 1;elsereturn 0;
}int BFSvisit(int start,GH *G)//对图G从start号点开始广度优先遍历,返回访问过的节点个数
{int i,n;int *visit;int *q;int front,rear;AR *p;//空间分配n=G->vexnum;visit=(int *)malloc(n*sizeof(int));q=(int *)malloc(n*sizeof(int));//初始化for(i=0;i<n;i++)visit[i]=0; visit[start]=1;q[0]=start;front=0;rear=1;while(front!=rear){p=G->N[q[front]].next;//对出队点的直接后继进行遍历//printf("%s\n",G->vexname[q[front]]); front++;while (p){if (visit[p->index] == 0)//如果未被访问,添加访问标记,入队{visit[p->index]=1;q[rear]=p->index;rear++;}p=p->next;} }free(visit);free(q);return rear;//返回广度优先遍历访问到的节点个数
}void prim(GH *G, int start)//利用prim算法,求图G中以start为起点的最小生成树
{int i,n;int *pre;float *weight; AR *N,*p;float min;int next;n=G->vexnum;pre=(int *)malloc(n*sizeof(int));//前驱数组weight=(float *)malloc(n*sizeof(float));//距生成树的最短距离,值为0.0表示属于生成树N=(AR *)malloc(n*sizeof(AR));//表头数组,用于存放最小生成树 //1.初始化for (i = 0; i < n; i++){if (G->A[start][i] < maxx)pre[i] = start; elsepre[i] = -1;weight[i] = G->A[start][i];N[i].next=NULL;}weight[start]=0;//起点放到生成树中//2.寻找距离已生成树最近的顶点min=maxx;for (i = 0; i < n; i++){if (weight[i] != 0 && weight[i] < min)//从与原图中与最小生成树连通的节点中选择{min=weight[i];next=i;}}while (min < maxx){weight[next]=0;//3.放入最小生成树中//相应地添加节点p=(AR *)malloc(sizeof(AR));p->index=next;p->weight=G->A[pre[next]][next];p->next=N[pre[next]].next;N[pre[next]].next=p;if (G->type == 0){p=(AR *)malloc(sizeof(AR));p->index=pre[next];p->weight=G->A[pre[next]][next];p->next=N[next].next;N[next].next=p;}//4.更新weight和prep=G->N[next].next;while (p){if (weight[p->index] != 0 && weight[p->index] > p->weight){weight[p->index]=p->weight;pre[p->index]=next;}p=p->next;}//继续寻找min=maxx;for (i = 0; i < n; i++){if (weight[i] != 0 && weight[i] < min){min=weight[i];next=i;}}}//结果显示showTree(N,G->vexname,n);free(pre);free(weight);free(N);
}void showTree(AR *N, char **vexname, int n)//显示最小生成树,N表示表头数组,vexname中存放顶点名称,n表示节点数量
{int i;AR *p;printf("最小生成树的邻接表形式如下:\n");for (i = 0; i < n; i++){ p=N[i].next;if (p){printf("%s", vexname[i]);while (p){printf("--%s(%.2f)", vexname[p->index], p->weight);p = p->next;}printf("\n");}}printf("\n");
}void kruskal(GH *G)//利用kruskal算法,求图G中的最小生成树
{int i;int m,n;int p,q;int *flag; AR *N,*t;m=G->arcnum;n=G->vexnum;flag=(int *)malloc(n*sizeof(int *));//位于同一连通分量的节点的flag值相同N=(AR *)malloc(n*sizeof(AR));//表头数组,用于存放最小生成树//1.初始化for (i = 0; i < n; i++){flag[i] = i;//各自为营N[i].next = NULL;}//2.对存放边的三元组G->E进行堆排序heapsort(G->E,m);//3.挑取边for (i = 1; i <= m; i++){ //读取两端节点编号p=G->E[i].i;q=G->E[i].j;if(flag[p]!=flag[q])//4.若两端位于不同的连通分支,表示该边可选{changeflag(q,flag[p],flag,N,n);//将q号节点所在的连通分支上的点的flag改为flag[p]//相应地添加节点t=(AR *)malloc(sizeof(AR));t->index=q;t->weight=G->A[p][q];t->next=N[p].next;N[p].next=t;//无向图需要双边添加节点if (G->type == 0){t = (AR *)malloc(sizeof(AR));t->index=p;t->weight=G->A[p][q];t->next=N[q].next;N[q].next=t;}}}//结果显示showTree(N,G->vexname,n);free(flag);free(N);
}void heapsort(EG *E, int n)//对含有n条边的三元组E进行堆排序
{int i;//首先调整所有的父节点for (i = n / 2; i >= 1; i--)adjust(i,E,n);//然后进行n-1趟堆排序,找出n-1个最大值for (i = 1; i <= n - 1; i++){ //首先进行首尾交换E[0]=E[1];E[1]=E[n-i+1];E[n-i+1]=E[0];//然后进行调整adjust(1,E,n-i);}
}void adjust(int index, EG *E, int n)//对利用顺序表E表示的含有n个节点的完全二叉树的index号节点进行筛选
{int i,j;i=index;j=2*i;E[0]=E[i];//哨兵while (j <= n){if (j + 1 <= n)//保证j始终指向较大的孩子{if(E[j+1].edge>E[j].edge)j=j+1;}if (E[j].edge > E[0].edge)//如果大于哨兵{E[i]=E[j];//将值赋给父节点i=j;//更新循环变量j=2*i;}else//E[j].edge <= E[0].edgebreak;//跳出循环}//i没有孩子或者E[j].edge <= E[0].edgeE[i]=E[0];
}void changeflag(int m,int y,int *flag, AR *N,int n)//将图G中m号节点所在的连通分支上的点的flag统一赋值为y,N为表头数组,n表示原图中节点个数
{ //利用BFS,访问m号节点所在的连通分支,更改其flag值int *q;int front,rear;AR *p;q=(int *)malloc(n*sizeof(int));//初始化flag[m]=y;q[0]=m;front=0;rear=1;while (front != rear){p=N[q[front]].next;front++;while (p){if (flag[p->index] != y){flag[p->index]=y;q[rear]=p->index;rear++;}p=p->next;}}free(q);
}
四、两种算法的比较
两种算法从不同的角度提出了最小生成树的构造方法:
Prim 算法从选节点的角度出发,每次都选距离生成树最近的节点,从而保证了最后的结果为最小生成树,时间复杂度与顶点个数 N 有关;每次选择最近顶点,需要进行一次遍历,比较 N 次,之后将其添加进最小生成树,更新 weight 和 pre,一次遍历,N 次操作,总共需要选择 N-1 次,所以 prim 算法的时间复杂度为 O ( N 2 ) O(N^2) O(N2);
Kruskal 算法从另一个角度出发,选取边,每次都选取权值最小的、添加之后不构成圈的边进行添加,同样保证了结果为最小生成树,时间复杂度与边的个数 e 有关;为方便后续处理,首先对边按权值大小进行排序,时间复杂度为 O ( e l o g 2 e ) O(elog_2e) O(elog2e),然后对每条边进行判断并决定是否添加,e 次操作,所以总的时间复杂度为 O ( e l o g 2 e ) O(elog_2e) O(elog2e);
综合所述,Prim 算法适合顶点相对较少而边相对稠密的网的最小生成树的求解,而 Kruskal 算法适合于求边比较稀疏的网的最小生成树;
五、小结
1、 首先进行一次 BFS,返回遍历到的节点数目,据此判断图是否连通,非连通图不具有最小生成树;
2、 对于 prim 算法,将最小生成树中的节点的 weight[i] 置为0,表示其在最小生成树中;
3、 最小生成树中每增添一个节点,更新其直接后继的 weight 和 pre;
4、 kruskal 算法,为图的结构体添加成员 E ——存放边的结构体数组,成员为两节点编号和边的权值,这样在创建图时,可以直接创建 E,同时,图的数据文件中添加边的条数;
5、 在执行 kruskal 算法时,可对 E 按照边的权值进行堆排序,避免了从邻接矩阵中再次读取边的操作,降低了算法的时间复杂度;
6、 通过 BFS 来实现某一连通分支上所有点的 flag 值的更改;
相关文章:
数据结构应用实例(四)——最小生成树
Content: 一、问题描述二、算法思想三、代码实现四、两种算法的比较五、小结 一、问题描述 利用 prim 算法和 kruskal 算法实现最小生成树问题; 二、算法思想 首先判断图是否连通,只有在连通的情况下才进行最小树的生成; 三、代…...

为OneAPI配置MySQL数据库及设置开机启动
OneAPI启动时,如果发现没有数据库他会在项目根目录自动创建SqlLit,为提高OneAPI的性能及管理,这里给出一个使用MySQL数据库的案例,同时本文介绍如何在源码部署的情况下,设置OneAPI的开机自动启动。 OneAPI的源代码安装…...
完整的k8s搭建服务器流程
一、准备 1、禁用selinux #临时禁用 setenforce 0 #永久禁用 sed -i s/enforcing/disabled/ /etc/selinux/config #检查selinux是否已禁用 sestatus 2、禁用交换分区 #命令行临时禁用 swapoff -a #永久禁用 vim /etc/fstab 注释掉有swap字样的那行,重启 3、允许…...

【Petri网导论学习笔记】Petri网导论入门学习(一)
Petri 网导论 如需学习转载请注明原作者并附本帖链接!!! 如需学习转载请注明原作者并附本帖链接!!! 如需学习转载请注明原作者并附本帖链接!!! 发现网上关于Petri网的学习…...

Zabbix监控自动化
监控在运维工作中所占的比例为 30%左右,监控做得好,会省很多事,让工作能有序地进行。理想的监控应该是自动化的,只需要配置规则,即可自动完成所有的事情,比如主机的自动添加和注册、模板的自动添加、分组的…...
pytorch pyro 贝叶斯神经网络 bnn beyesean neure network svi 定制SVI目标和培训循环,变更推理
定制SVI目标和培训循环 Pyro支持各种基于优化的贝叶斯推理方法,包括Trace_ELBO作为SVI(随机变分推理)的基本实现。参见文件(documents的简写)有关各种SVI实现和SVI教程的更多信息I, 二,以及罗马数字3了解SVI的背景。 在本教程中…...

Openeuler22 部署 RackTables0.22.0
目录 0、前言 一、部署lamp环境,lamp环境测试 1、部署Apache,apache环境测试 2、部署php、mysql,php环境测试 二、放文件 三、配置mysql 四、安装racktables 第一步、点击proceed继续 第二步、点击proceed 第三步、根据提示进行操作…...

从传统到智能:高标准农田灌区信息化助力农业现代化
从传统农业的粗放式管理,到如今智能化、精准化的现代农业转型,高标准农田灌区信息化建设无疑是推动这一历史进程的关键力量。它不仅标志着农业生产方式的根本性变革,还深刻影响着农业资源的高效利用与可持续发展策略,为实现农业现…...

堆排序-建堆,增删替换
我们 之前写过根据 堆排序的优先级队列,但是如果我们想要建立一个堆怎么办呢? 如何实现上浮 下潜 具体看这篇文章 堆排序-优先级队列-CSDN博客 建堆 我们有两种方法建立一个堆 1.我们基于add方法建立一个堆,一次次的add,然后对…...

使用AI写WebSocket知识是一种怎么样的体验?
一、WebSocket基础知识 1. WebSocket概念 1.1 为什么会出现WebSocket 一般的Http请求我们只有主动去请求接口,才能获取到服务器的数据。例如前后端分离的开发场景,自嘲为切图仔的前端大佬找你要一个配置信息的接口,我们后端开发三下两下开…...

若依系统(Security)增加微信小程序登录(自定义登录)
若依系统(分离版后端)自带的账号验证是基于 UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(username, password); 验证,然后在系统中controller或service类中 SecurityUtils 工具类中直接可获取用户或用户…...
道可云人工智能元宇宙每日资讯|2024互联网岳麓峰会在长沙召开
道可云元宇宙每日简报(2024年9月10日)讯,今日元宇宙新鲜事有: 2024互联网岳麓峰会在长沙召开 9月9日,2024互联网岳麓峰会在长沙召开,湖南省副省长曹志强在峰会表示,今年上半年湖南省人工智能产…...
MySQL JDBC URL各参数详解
jdbc:mysql://localhost:3306/test?userroot&password123456&useUnicodetrue&characterEncodinggbk &autoReconnecttrue&failOverReadOnlyfalse&serverTimezoneUTC&drivercom.mysql.cj.jdbc.Driver 参数名称参数说明缺省值user指定用于连接数据库…...
celery control.shutdown
Celery 提供了 control 模块,允许你发送控制命令给正在运行的 worker。其中 shutdown 命令可以用来关闭一个或多个 worker。下面是如何使用 control.shutdown 来关闭 worker 的详细说明。 使用 control.shutdown 1. 导入必要的模块 首先,你需要导入 C…...
数据库设计与软件工程阶段的对应关系
数据库设计的很多阶段确实可以与软件工程的各阶段对应起来,这体现了数据库设计作为软件工程中一个核心组成部分的紧密关联性。 1. 需求分析阶段 数据库设计:需求分析是数据库设计的初始阶段,主要任务是收集和分析用户的需求,包括…...

基于ASP+ACCESS的教师信息管理系统
摘要 随着我国社会主义市场经济的发展和改革开放的不断深入,计算机的应用已遍及国民经济的各个领域,计算机来到我们的工作和生活中,改变着我们和周围的一切。在以前,学校用手工处理教师档案以及工资发放等繁多的工作和数据时&…...

【智能体】浅谈大模型之AI Agent
随着ChatGPT推出插件和函数调用功能,构建以LLM(大语言模型)为核心控制器的AI Agent愈发成为一个拥有无限可能的概念。 AI Agent是一种超越简单文本生成的人工智能系统。它使用大型语言模型(LLM)作为其核心计算引擎&am…...
大疆 嵌入式 笔记 面试题目汇总大全[嵌入式找工作必看] 比较有难度适合进阶收藏学习
24届大疆车载嵌入式系统安全面试经验 投递岗位:(大疆车载)嵌入式系统安全 投递时间:大疆大概在7月-8月月初开秋招岗位。8月月中开始笔试,8月月末开始面试。(理论上9月,10月开奖。)…...
线程池以及详解使用@Async注解异步处理方法
目录 一.什么是线程池: 二.使用线程池的好处: 三.线程池的使用场景: 四.使用线程池来提高Springboot项目的并发处理能力: 1.在application.yml配置文件中配置: 2.定义配置类来接受配置文件内的属性值:…...
css鼠标移动过去变成手的图标
在css中定义 cursor:pointer;直接在html中指定 <div class"mt-2 mt-md-2 mt-lg-1 fs-md-1 fs-lg-2 " style"cursor:pointer;"></div>...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...