两种最小生成树的方法——普里姆算法、克鲁斯卡尔算法

2024-01-01 03:18

本文主要是介绍两种最小生成树的方法——普里姆算法、克鲁斯卡尔算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、最小生成树的概念

二、MST性质

1、性质

2、 证明

二、普里姆(Prim)算法

1、算法思想

2、图形解析

3、逐步实现

(1)建立无向图的邻接矩阵

(2)找出辅助数组中与closedge代价最小的顶点的位置

(3)普里姆核心算法

4、总代码

5、时间复杂度

三、克鲁斯卡尔(Kruskal)算法

1、算法思想

2、图形解析

3、逐步实现

(1)构建无向图的邻接表

(2)找到可连接的边

(3)判断最小生成树是否完成

(4)克鲁斯卡尔核心算法

4、总代码

5、时间复杂度


一、最小生成树的概念

在连通网的所有生成树中,所有边的代价和最小的生成树。

二、MST性质

       构建最小生成树的算法有多种,比如普里姆算法、克鲁斯卡尔算法。其中多数算法利用了最小生成树的下列一种简称MST的性质。 

1、性质

       假设N=( V , { E } )是一个连通网,U是顶点集V的一个非空子集。若(uv)是一条具有最小权值的边,其中uUvV-U,则必存在一颗包含边(uv)的最小生成树。

                    图1  连通图N 

2、 证明

利用反证法证明。

      假设网N的任意一颗最小生成树都不包含(uv)。

      设T是连通网上的一颗最小生成树。

      当将边(uv)加入到T中时,由生成树的定义,T中必存在一条包含(uv)的回路。另一方面,由于T是生成树,则在T上必存在另一条边(u’v’),其中u’∈Uv’∈V-U,且uu’之间,vv’之间均有路径相通。删去边(u’,v’),便可消除上述回路,同时得到另一颗生成树T’。因为(uv)的代价不高于(u’,v’),则T’的代价也不高于TT’是包含(uv)的一颗最小生成树。

      由此与假设矛盾,性质正确。

                               

                 图2  假设的最小生成树T                                           图3  实际的最小生成树T 

二、普里姆(Prim)算法

1、算法思想

(1)设G=(V,E)是连通图,TE是N上最小生成树中边的集合。

(2)初始令U={u0},(u0∈V),TE={ }。

(3)在所有u∈U,v∈V-U的边(u,v)∈E中,找一条代价最小的边(u0,v0)。

(4)将(u0,v0)并入集合TE,同时v0并入U。

(5)重复上述操作直至U=V为止,则T=(V,TE)为N的最小生成树。

2、图形解析

                                               图4  普里姆算法的图解 

3、逐步实现

(1)建立无向图的邻接矩阵

int CreateGraph(MGraph& G) {//创建无向网的邻接矩阵 int v1,v2,w;  //邻接两点v1,v2,以及所构成边的权值w printf("请输入顶点数和边数:");scanf("%d%d", &G.vexnum, &G.arcnum);for (int i = 0; i < G.vexnum; ++i){ //构造顶点向量 printf("请输入第%d个顶点的值:",i+1);scanf("%d", &G.vexs[i]);}for (int i = 0; i < G.vexnum; ++i){//初始化邻接矩阵, 将各边的权值都赋值为 INT_MAXfor (int j = 0; j < G.vexnum; ++j)G.arcs[i][j]=  INFINITY ;}for (int k = 0; k < G.arcnum; ++k) {//构造邻接矩阵 printf("请输入相邻两点的值,以及他们所构成边的权值:");scanf("%d%d%d", &v1, &v2, &w);int i = LocateVex(G, v1);int j = LocateVex(G, v2);G.arcs[i][j]= w;G.arcs[j][i] = G.arcs[i][j];  //无向网邻接矩阵对称 }return 0;
}

(2)找出辅助数组中与closedge代价最小的顶点的位置

int minimum(struct closedge[], int n){ //找出辅助数组中与closedge代价最小的顶点的位置int i = 0, j, min, k;while (!closedge[i].lowcost)  //由于初始设lowcost都为0,所以从第一个不为零的值开始i++;min = closedge[i].lowcost;k = i;for (j = 1; j < n; j++)  //依次遍历、比较,最后输出最小值if (closedge[j].lowcost)if (min > closedge[j].lowcost){min = closedge[j].lowcost;k = j;}return k;
}

(3)普里姆核心算法

(此处使用伪代码)

void MiniSpanTree_Prim(MGraph  G,VertexType u){     
//用普里姆算法从第u个顶点出发构造网G的最小生成树Tk = LocateNode(G,u);for(j = 0;j<G.vexnum;++j)     //辅助数组初始化if(j!=k)   closedge[j] = {u,G.arcs[k][j].adj};     //{adjvex,lowcost}closedge[k].lowcost = 0;    //初始,U={u}for(i=1;i<G.vexnum;++i){    //选择其余G.vexnum – 1个顶点k = minimum(closedge);     //求出T的下一个结点,第k个顶点printf(closedge[k].adjvex,G.vexs[k]);    //输出生成树的边closedge[k].lowcost = 0;    //第k个顶点并入U集for(j=0;j<G.vexnum;++j)if(G.arcs[k][j].adj<closedge[j].lowcost)      //新顶点并入U后重新选择最小边closedge[j] = {G.vexs[k],G.arcs[k][j].adj};}
}  //MiniSpanTree_Prim

4、总代码

#include<stdio.h>
#include<limits.h>#define INFINITY INT_MAX
#define MAX_VERTEX_NUM 20typedef struct {int vexs[MAX_VERTEX_NUM];  //顶点向量 int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];  //邻接矩阵 int vexnum, arcnum;  //顶点数,边数 
}MGraph;//记录从顶点集U到V-U的代价最小的边的辅助数组定义 
struct closedge{int adjvex;  //顶点int lowcost;  //其他顶点与adjvex顶点相连的边的权值 
}closedge[MAX_VERTEX_NUM];int LocateVex(MGraph G,int v) {//顶点v在网G中的位序 for (int i = 0; i < G.vexnum; ++i) {if (v == G.vexs[i])return i;}return -1;
}int CreateGraph(MGraph& G) {//创建无向网的邻接矩阵 int v1,v2,w;  //邻接两点v1,v2,以及所构成边的权值w printf("请输入顶点数和边数:");scanf("%d%d", &G.vexnum, &G.arcnum);for (int i = 0; i < G.vexnum; ++i){ //构造顶点向量 printf("请输入第%d个顶点的值:",i+1);scanf("%d", &G.vexs[i]);}for (int i = 0; i < G.vexnum; ++i){//初始化邻接矩阵, 将各边的权值都赋值为 INT_MAXfor (int j = 0; j < G.vexnum; ++j)G.arcs[i][j]=  INFINITY ;}for (int k = 0; k < G.arcnum; ++k) {//构造邻接矩阵 printf("请输入相邻两点的值,以及他们所构成边的权值:");scanf("%d%d%d", &v1, &v2, &w);int i = LocateVex(G, v1);int j = LocateVex(G, v2);G.arcs[i][j]= w;G.arcs[j][i] = G.arcs[i][j];  //无向网邻接矩阵对称 }return 0;
}void PrintG(MGraph G) {//打印邻接矩阵 printf("邻接矩阵为:\n");for (int i = 0; i < G.vexnum; i++) {for (int j = 0; j < G.vexnum; j++) {if (G.arcs[i][j]== INFINITY)//对于权值为INT_MAX的边,输出 ∞printf("∞\t");elseprintf("%d\t",G.arcs[i][j]);}printf("\n");}
}int minimum(struct closedge[], int n){ //找出辅助数组中与closedge代价最小的顶点的位置int i = 0, j, min, k;while (!closedge[i].lowcost)i++;min = closedge[i].lowcost;k = i;for (j = 1; j < n; j++)if (closedge[j].lowcost)if (min > closedge[j].lowcost){min = closedge[j].lowcost;k = j;}return k;
}void MiniSpanTree_PRIM(MGraph G,int u){//普里姆算法求最小生成树 ,从顶点u出发构造最小生成树int k = LocateVex(G,u);for(int j= 0;j<G.vexnum;++j)//初始化辅助数组 if(j!= k){closedge[j].adjvex = u;closedge[j].lowcost = G.arcs[k][j];}closedge[k].lowcost = 0;  //初始,U={u} printf("最小生成树为:\n");for(int i =1;i<G.vexnum;++i){//选择其余G.vexnum-1个顶点 k = minimum(closedge,G.vexnum);  //找出与u代价最小的邻接点 printf("V%d----V%d", LocateVex(G,closedge[k].adjvex) + 1, k + 1);printf(" = %d\n",G.arcs[LocateVex(G,closedge[k].adjvex)][k]);closedge[k].lowcost = 0;  //第k个顶点并入Ufor(int j = 0;j<G.vexnum;++j)if(G.arcs[k][j]< closedge[j].lowcost){//新顶点并入U后重新选择最小值 closedge[j].adjvex = G.vexs[k];closedge[j].lowcost = G.arcs[k][j];}}
}int main(){MGraph G;CreateGraph(G);PrintG(G);MiniSpanTree_PRIM(G,1);return 0;
}

5、时间复杂度

普里姆算法的性能取决于如何选取下一条最小权值的边

时间复杂度为O(n^2)

与网中的边数无关

因此适用于求边稠密的网的最小生成树

三、克鲁斯卡尔(Kruskal)算法

1、算法思想

(1)设连通网G=(V,E),令最小生成树初始状态为只有n个顶点而无边的非连通图T=(V,{ }),每个顶点自成一个连通分量。

(2)在E中选取代价最小的边,若该边依附的顶点落在T中不同的连通分量上(即:不能形成环),则将此边加入的T中;否则,舍去此边,选取下一条代价最小的边。

(3)依次类推,直到T中所有顶点都在同一连通分量上为止。

2、图形解析

                                                  图5   克鲁斯卡尔算法的图解 

3、逐步实现

(1)构建无向图的邻接表

int CreateGraph(ALGraph &G){//创建无向网的邻接表 printf("输入图的顶点数和边数:");scanf("%d%d",&G.vexsnum,&G.arcsnum);G.p = (Edge*)malloc(sizeof(Edge)*(G.arcsnum + 1));  //分配G.arcsnum + 1个空间,多分配出1个空间 G.m = (int*)malloc(sizeof(int)*(G.vexsnum));  //分配G.vexsnum个空间 for(int i = 0;i<G.vexsnum;++i){printf("请输入第%d个顶点值",i+1);scanf("%d",&G.m[i]);}for(int i = 0;i<G.arcsnum;++i){printf("请输入邻接两点以及所构成的边的权值:");scanf("%d%d%d",&G.p[i].v1,&G.p[i].v2,&G.p[i].weight);}for(int i = 0;i<G.arcsnum;++i){//使用冒泡排序将权重从小到大存到边集数组里 for(int j = G.arcsnum-1;j>i;--j){if(G.p[i].weight>G.p[j].weight){G.p[G.arcsnum] = G.p[i];  //暂时将大的权值赋值到多出来的那个空间 G.p[i] = G.p[j];G.p[j] = G.p[G.arcsnum];}}}return 0;
}

(2)找到可连接的边

此处用到并查集的算法

int Find(int *parent,int g){//通过parent[]找到可连接的边while(parent[g]!=0){//利用循环找到g的根 g = parent[g]; }return g;
}

(3)判断最小生成树是否完成

int IsFinish(ALGraph &G,int *parent){//判断生成树是否完成,完成的标志是生成树的边等于顶点的数量减1int i,n = 0;for(i = 0;i<G.vexsnum;++i){if(parent[i]) n++;}if(n == G.vexsnum-1) return 1;	//如果等于就表示最小树生成结束 return 0;
}

(4)克鲁斯卡尔核心算法

(此处使用伪代码)

int MiniSpanTree_Kruskal(ALGraph &G){        
//用克鲁斯卡尔算法构造网G的最小生成树Tint parent[G.vexsnum];       //辅助数组,用于判断两个点是否在同一个连通分量中for(int i = 0;i<G.vexsnum;++i)       //初始化辅助数组parent[i] = 0;for(int i = 0;i<G.arcsnum;++i){a=Find(parent,LocateVex(G,G.p[i].v1));  //找到v1、v2的根a、bb=Find(parent,LocateVex(G,G.p[i].v2));if(a != b){    //判断是否成环,如果a==b则表是v1和v2在同一颗生成树上parent[a] = b;   //不在同一棵树上,合并为同一棵树printf(G.p[i].v1,G.p[i].v2,G.p[i].weight);     //输出生成树的边}if(IsFinish(G,parent))   //判断是否完成,完成的标志是生成树的边等于顶点的数量减1return 0 ;  }
}   //MiniSpanTree_Kruskal

4、总代码

#include<stdio.h>
#include<stdlib.h> typedef struct Edge{int v1;int v2;int weight;
}Edge;
typedef struct ALGraph{int vexsnum;  //顶点数 int arcsnum;  //边数 Edge *p;  //指针p指向边集数组 int *m;  //指针m指向顶点集数组 
}ALGraph;int CreateGraph(ALGraph &G){//创建无向网的邻接表 printf("输入图的顶点数和边数:");scanf("%d%d",&G.vexsnum,&G.arcsnum);G.p = (Edge*)malloc(sizeof(Edge)*(G.arcsnum + 1)); //分配G.arcsnum + 1个空间,多分配出1个空间 G.m = (int*)malloc(sizeof(int)*(G.vexsnum));  //分配G.vexsnum个空间 for(int i = 0;i<G.vexsnum;++i){printf("请输入第%d个顶点值",i+1);scanf("%d",&G.m[i]);}for(int i = 0;i<G.arcsnum;++i){printf("请输入邻接两点以及所构成的边的权值:");scanf("%d%d%d",&G.p[i].v1,&G.p[i].v2,&G.p[i].weight);}for(int i = 0;i<G.arcsnum;++i){//使用冒泡排序将权重从小到大存到边集数组里 for(int j = G.arcsnum-1;j>i;--j){if(G.p[i].weight>G.p[j].weight){G.p[G.arcsnum] = G.p[i];  //暂时将大的权值赋值到多出来的那个空间 G.p[i] = G.p[j];G.p[j] = G.p[G.arcsnum];}}}return 0;
}int Find(int *parent,int g){//通过parent[]找到可连接的边while(parent[g]!=0){//利用循环找到g的根 g = parent[g]; }return g;
}int IsFinish(ALGraph &G,int *parent){//判断生成树是否完成,完成的标志是生成树的边等于顶点的数量减1int i,n = 0;for(i = 0;i<G.vexsnum;++i){if(parent[i]) n++;}if(n == G.vexsnum-1) return 1;	//如果等于就表示最小树生成结束 return 0;
}int LocateVex(ALGraph &G,int g){//找到顶点的下标int i;for(i = 0;i<G.vexsnum;++i){if(G.m[i] == g)return i;}return -1;
}int MiniSpanTree_Kruskal(ALGraph &G){int a,b;int parent[G.vexsnum];  //辅助数组,判断两个点是否在同一个连通分量中 for(int i = 0;i<G.vexsnum;++i){//初始化parent[],全部赋值为0 parent[i] = 0;}printf("最小生成树为:\n");for(int i = 0;i<G.arcsnum;++i){a = Find(parent,LocateVex(G,G.p[i].v1));  //找到v1、v2的根a、b b = Find(parent,LocateVex(G,G.p[i].v2));if(a != b){//判断是否成环,如果a==b则表是v1和v2在同一颗生成树上,如果a和b连接则为生成环,不符合生成树parent[a] = b;  //不在一棵树上,合并为在同一棵树上 printf("V%d----V%d = %d\n",LocateVex(G,G.p[i].v1),LocateVex(G,G.p[i].v2),G.p[i].weight);} if(IsFinish(G,parent))//判断是否完成,完成后返回 return 0 ;}
}int main(){ALGraph G;CreateGraph(G);MiniSpanTree_Kruskal(G);return 0;
}

5、时间复杂度

克鲁斯卡尔算法时间复杂度主要取决于将权值边按从小到大的顺序排列

时间复杂度为O(eloge)

e为网中边的数目

因此适用于求边稀疏的网的最小生成树。

这篇关于两种最小生成树的方法——普里姆算法、克鲁斯卡尔算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/558020

相关文章

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

Apache Tomcat服务器版本号隐藏的几种方法

《ApacheTomcat服务器版本号隐藏的几种方法》本文主要介绍了ApacheTomcat服务器版本号隐藏的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1. 隐藏HTTP响应头中的Server信息编辑 server.XML 文件2. 修China编程改错误

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁

JavaScript DOM操作与事件处理方法

《JavaScriptDOM操作与事件处理方法》本文通过一系列代码片段,详细介绍了如何使用JavaScript进行DOM操作、事件处理、属性操作、内容操作、尺寸和位置获取,以及实现简单的动画效果,涵... 目录前言1. 类名操作代码片段代码解析2. 属性操作代码片段代码解析3. 内容操作代码片段代码解析4.

SpringBoot3集成swagger文档的使用方法

《SpringBoot3集成swagger文档的使用方法》本文介绍了Swagger的诞生背景、主要功能以及如何在SpringBoot3中集成Swagger文档,Swagger可以帮助自动生成API文档... 目录一、前言1. API 文档自动生成2. 交互式 API 测试3. API 设计和开发协作二、使用

python忽略warnings的几种方法

《python忽略warnings的几种方法》本文主要介绍了几种在Python忽略警告信息的方法,,可以使用Python内置的警告控制机制来抑制特定类型的警告,下面就来介绍一下,感兴趣的可以了解一下... 目录方法 1: 使用 warnings 模块过滤特定类型和消息内容的警告方法 2: 使用 warnin