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

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

相关文章

Pytest多环境切换的常见方法介绍

《Pytest多环境切换的常见方法介绍》Pytest作为自动化测试的主力框架,如何实现本地、测试、预发、生产环境的灵活切换,本文总结了通过pytest框架实现自由环境切换的几种方法,大家可以根据需要进... 目录1.pytest-base-url2.hooks函数3.yml和fixture结论你是否也遇到过

鸿蒙中Axios数据请求的封装和配置方法

《鸿蒙中Axios数据请求的封装和配置方法》:本文主要介绍鸿蒙中Axios数据请求的封装和配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.配置权限 应用级权限和系统级权限2.配置网络请求的代码3.下载在Entry中 下载AxIOS4.封装Htt

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

Redis实现延迟任务的三种方法详解

《Redis实现延迟任务的三种方法详解》延迟任务(DelayedTask)是指在未来的某个时间点,执行相应的任务,本文为大家整理了三种常见的实现方法,感兴趣的小伙伴可以参考一下... 目录1.前言2.Redis如何实现延迟任务3.代码实现3.1. 过期键通知事件实现3.2. 使用ZSet实现延迟任务3.3

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释

Feign Client超时时间设置不生效的解决方法

《FeignClient超时时间设置不生效的解决方法》这篇文章主要为大家详细介绍了FeignClient超时时间设置不生效的原因与解决方法,具有一定的的参考价值,希望对大家有一定的帮助... 在使用Feign Client时,可以通过两种方式来设置超时时间:1.针对整个Feign Client设置超时时间

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n