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

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读取本地文件并转换为MultipartFile对象的方法

《使用Java读取本地文件并转换为MultipartFile对象的方法》在许多JavaWeb应用中,我们经常会遇到将本地文件上传至服务器或其他系统的需求,在这种场景下,MultipartFile对象非... 目录1. 基本需求2. 自定义 MultipartFile 类3. 实现代码4. 代码解析5. 自定

Python文本相似度计算的方法大全

《Python文本相似度计算的方法大全》文本相似度是指两个文本在内容、结构或语义上的相近程度,通常用0到1之间的数值表示,0表示完全不同,1表示完全相同,本文将深入解析多种文本相似度计算方法,帮助您选... 目录前言什么是文本相似度?1. Levenshtein 距离(编辑距离)核心公式实现示例2. Jac

C#高效实现Word文档内容查找与替换的6种方法

《C#高效实现Word文档内容查找与替换的6种方法》在日常文档处理工作中,尤其是面对大型Word文档时,手动查找、替换文本往往既耗时又容易出错,本文整理了C#查找与替换Word内容的6种方法,大家可以... 目录环境准备方法一:查找文本并替换为新文本方法二:使用正则表达式查找并替换文本方法三:将文本替换为图

SQL Server 查询数据库及数据文件大小的方法

《SQLServer查询数据库及数据文件大小的方法》文章介绍了查询数据库大小的SQL方法及存储过程实现,涵盖当前数据库、所有数据库的总大小及文件明细,本文结合实例代码给大家介绍的非常详细,感兴趣的... 目录1. 直接使用SQL1.1 查询当前数据库大小1.2 查询所有数据库的大小1.3 查询每个数据库的详

Java实现本地缓存的四种方法实现与对比

《Java实现本地缓存的四种方法实现与对比》本地缓存的优点就是速度非常快,没有网络消耗,本地缓存比如caffine,guavacache这些都是比较常用的,下面我们来看看这四种缓存的具体实现吧... 目录1、HashMap2、Guava Cache3、Caffeine4、Encache本地缓存比如 caff

C#使用Spire.XLS快速生成多表格Excel文件

《C#使用Spire.XLS快速生成多表格Excel文件》在日常开发中,我们经常需要将业务数据导出为结构清晰的Excel文件,本文将手把手教你使用Spire.XLS这个强大的.NET组件,只需几行C#... 目录一、Spire.XLS核心优势清单1.1 性能碾压:从3秒到0.5秒的质变1.2 批量操作的优雅

Java 中编码与解码的具体实现方法

《Java中编码与解码的具体实现方法》在Java中,字符编码与解码是处理数据的重要组成部分,正确的编码和解码可以确保字符数据在存储、传输、读取时不会出现乱码,本文将详细介绍Java中字符编码与解码的... 目录Java 中编码与解码的实现详解1. 什么是字符编码与解码?1.1 字符编码(Encoding)1

Python Flask实现定时任务的不同方法详解

《PythonFlask实现定时任务的不同方法详解》在Flask中实现定时任务,最常用的方法是使用APScheduler库,本文将提供一个完整的解决方案,有需要的小伙伴可以跟随小编一起学习一下... 目录完js整实现方案代码解释1. 依赖安装2. 核心组件3. 任务类型4. 任务管理5. 持久化存储生产环境

Python使用python-pptx自动化操作和生成PPT

《Python使用python-pptx自动化操作和生成PPT》这篇文章主要为大家详细介绍了如何使用python-pptx库实现PPT自动化,并提供实用的代码示例和应用场景,感兴趣的小伙伴可以跟随小编... 目录使用python-pptx操作PPT文档安装python-pptx基础概念创建新的PPT文档查看

Python批量替换多个Word文档的多个关键字的方法

《Python批量替换多个Word文档的多个关键字的方法》有时,我们手头上有多个Excel或者Word文件,但是领导突然要求对某几个术语进行批量的修改,你是不是有要崩溃的感觉,所以本文给大家介绍了Py... 目录工具准备先梳理一下思路神奇代码来啦!代码详解激动人心的测试结语嘿,各位小伙伴们,大家好!有没有想