数据结构与算法(7-3)最小生成树(普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法)
目录
一、最小生成树简介
二、普里姆算法(Prim)
1、原理
2、存储
2-1、图顶点和权:
2-3、 最小生成树:
3、Prim()函数
3-1、新顶点入树
3-2、保留最小权
3-3、 找到最小路径
3-4、判断退出或递归
4、代码
三、克鲁斯卡尔算法
1、原理
2、过程
2-1、存储结构
2-2、从小到大排边
2-3、Kruskal算法以及防止连通(防止连通是难点)
3、代码
参考资料
一、最小生成树简介
用途:找到连通图的最短路径之和。
注:最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。
应用:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
最小生成树和最短路径区别:
最小生成树:连通图的最短路径。
最短路径:两任意结点之间(可以非邻接)的最短路径。
二、普里姆算法(Prim)
1、原理
把树视为一个整体(顶点),从根部(自选定)出发,一点一点向周围搜索,找到周围权最小的顶点(最小路径),把它纳入Prim树,把Prim树的整体视作一个根结点,继续往下递归搜索。
(欣赏下面的三样图)
1、
2、
3、
2、存储
2-1、图顶点和权:
//图(顶点和权)
typedef struct
{char vertex[MAXSIZE];int weight[MAXSIZE][MAXSIZE]; //权可以代替边(自身为0,相连有值,不相连无穷大)
}Graph;
Graph G;
不需要再加边,因为权=0即自身,权=无穷大即断开,所以不需要再加边来表示连接关系。
2-3、 最小生成树:
//最小生成树
typedef struct
{char vertex[MAXSIZE]; //最小生成树内部顶点int weight[MAXSIZE]; //最小生成树权重(内部为0,相连有值,不相连无穷大)int minway[MAXSIZE]; //最小生成树最短路径
}MST;
MST M;
3、Prim()函数
3-1、新顶点入树
最小生成树也是越积越多,顶点vertex纳入最小生成树,则M.vertex[index]=vertex,其对应权M.weight[index]=0。
M.vertex[]:存放最小生成树内部的顶点。
M.weight[]:存放最小生成树内部距离外界的最短路径。
3-2、保留最小权
把新入树的顶点的权和树本身的权进行比较,保留更小的一方(因为要生成的是最短路径和)。
3-3、 找到最小路径
记录从最小生成树内部的顶点连向外界顶点的最短路径(最小权),保留下来即为本次行走的权。(也是下一次需要纳入的权)(先纳入顶点,在找下一次纳入的权)
3-4、判断退出或递归
如果Prim()函数调用次数达到顶点长度则退出递归,否则一直递归调用Prim()函数。
//获取最小生成树的最小权值下标
int FindMin()
{int i, min = 0;for (i = 0; i < length; i++){if (M.weight[i] != 0) //跳过0(即跳过内部顶点){if (M.weight[min] == 0 || M.weight[min] > M.weight[i]) //跳过0,取最小min = i;}}return min;
}//普里姆算法
void Prim(char vertex, int index) //放入根
{int i, j, min;//获取最小生成树新的顶点M.vertex[index] = vertex; //新顶点//获取最小生成树新的权M.weight[index] = 0; //新权(纳入最小生成树内部,为0)for (i = 0; i < length; i++){ if (M.weight[i] > G.weight[index][i]) //获得最小权{M.weight[i] = G.weight[index][i]; //最小生成树的权}//标记最小路径if (M.weight[i] == 0)for (j = 0; j < length; j++){// 行!=列(0) i和j不能都在最小生成树内(不能连接自己) if (M.minway[index]>G.weight[i][j] && j!=i && ((M.weight[i]!=0)||(M.weight[j]!=0))) //i != jM.minway[index] = G.weight[i][j];}}printf("%c %d ", M.vertex[index], M.minway[index]);count++;//判断退出if (count >= length)return;//寻找下一个最小生成树下标(跳过0)min = FindMin();Prim(G.vertex[min], min);
}
4、代码
//普里姆算法(Prim)————图的最小生成树
//把树视为一个整体(顶点),从根部(自选定)出发,一点一点向周围搜索,
//找到周围权最小的顶点(最小路径),把它纳入Prim树,把Prim树的整体视作一个根结点,
//继续往下递归搜索。
//自实现,目前有一定的缺点:时间复杂度O(n^3)有些高
/*测试:
ABCDEFGHI
B 10 F 11
C 18 I 12 G 16
B 18 I 8 D 22
C 22 I 21 G 24 H 16 E 20
D 20 H 7 F 26
A 11 G 17 E 26
B 16 D 24 F 17 H 19
D 16 E 7 G 19
B 12 C 8 D 21
*/
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>#define MAXSIZE 20
#define MAX 65535 //代表无穷大
int length = 0; //顶点个数
int count = 0; //计数Prim最小生成树元素个数//图(顶点和权)
typedef struct
{char vertex[MAXSIZE];int weight[MAXSIZE][MAXSIZE]; //权可以代替边(自身为0,相连有值,不相连无穷大)
}Graph;
Graph G;//最小生成树
typedef struct
{char vertex[MAXSIZE]; //最小生成树内部顶点int weight[MAXSIZE]; //最小生成树权重(内部为0,相连有值,不相连无穷大)int minway[MAXSIZE]; //最小生成树最短路径
}MST;
MST M;//输入顶点
void InputVertex()
{int i;char ch;printf("请输入图的顶点:\n");scanf("%c", &ch);for (i = 0; i < MAXSIZE && ch != '\n'; i++){G.vertex[i] = ch;scanf("%c", &ch);}length = i;
}//图权重初始化
void GraphWeightInit()
{int i, j;for (i = 0; i < length; i++){for (j = 0; j < length; j++){if (i == j) //指向自己G.weight[i][j] = 0;elseG.weight[i][j] = MAX; //无穷大}}
}//根据数据查找图顶点下标
int FindIndex(char ch)
{int i;for (i = 0; i < length; i++){if (G.vertex[i] == ch)return i;}return -1;
}//获取最小生成树的最小权值下标
int GetMin()
{int i, min = 0;for (i = 0; i < length; i++){if (M.weight[i] != 0) //跳过0(即跳过内部顶点){if (M.weight[min] == 0 || M.weight[min] > M.weight[i]) //跳过0,取最小min = i;}}return min;
}//创建图
void CreateGraph()
{int i, j, index, weight;char ch;for (i = 0; i < length; i++){printf("请输入%c的邻接顶点及权重(空格分隔,换行结束):\n", G.vertex[i]);scanf("%c", &ch);while (ch != '\n'){while (ch == ' ') //为空格{scanf("%c", &ch); //输入字符continue;}index = FindIndex(ch);scanf("%d", &weight); //输入权重while (weight == 32) //32为空格的ASCII码{scanf("%d", &weight);continue;}G.weight[i][index] = weight; //存入权重scanf("%c", &ch); //(下一轮)输入字符}}
}//最小生成树初始化
void MST_Init()
{for (int i = 0; i < length; i++){M.weight[i] = MAX; //权初始设置为无穷大(无邻接结点)M.minway[i] = MAX; //最短路径值}
}//普里姆算法
void Prim(char vertex, int index) //放入根
{int i, j, min;//获取最小生成树新的顶点M.vertex[index] = vertex; //新顶点//获取最小生成树新的权M.weight[index] = 0; //新权(纳入最小生成树内部,为0)for (i = 0; i < length; i++){if (M.weight[i] > G.weight[index][i]) //刷新最小权{M.weight[i] = G.weight[index][i]; //覆盖最小生成树的权}//找到最小路径(最小生成树)if (M.weight[i] == 0) //最小生成树内部for (j = 0; j < length; j++){// 行!=列(0) i和j不能都在最小生成树内(不能连接自己)(i在内, j在外) if (M.minway[index] > G.weight[i][j] && j != i && ((M.weight[i] != 0) || (M.weight[j] != 0))) //i != jM.minway[index] = G.weight[i][j];}}printf("%c -> %d -> ", M.vertex[index], M.minway[index]);count++;//判断退出if (count >= length)return;//寻找下一个最小生成树下标(跳过0)min = GetMin();Prim(G.vertex[min], min); //递归Prim()函数
}//输出测试
void Print()
{for (int i = 0; i < length; i++){printf("\n%c结点邻接结点:\t", G.vertex[i]);for (int j = 0; j < length; j++){if (G.weight[i][j] != 0 && G.weight[i][j] != MAX) //有邻接结点{printf("%c %d\t", G.vertex[j], G.weight[i][j]);}}}
}int main()
{InputVertex(); //输入顶点GraphWeightInit(); //图权重初始化CreateGraph(); //创建图MST_Init(); //最小生成树初始化printf("\n最小生成树路径及权(Prim算法):\n");Prim(G.vertex[0], 0); //普里姆算法(最小生成树)//Print(); //测试输出return 0;
}
(这个算法是自实现的,时间复杂度有些高,O(n^3),先暂时不去优化,继续往后学吧)
三、克鲁斯卡尔算法
1、原理
以边为基础,从小到大依次选出最短路径,产生最小生成树。
原理图:
第1步:将边<E,F>加入R中。
边<E,F>的权值最小,因此将它加入到最小生成树结果R中。
第2步:将边<C,D>加入R中。
上一步操作之后,边<C,D>的权值最小,因此将它加入到最小生成树结果R中。
第3步:将边<D,E>加入R中。
上一 步操作之后,边<D,E>的权值最小,因此将它加入到最小生成树结果R中。
第4步:将边<B,F>加入R中。
上一步操作之后,边<C,E>的权值最小,但<C,E>会和已有的边构成回路;因此,跳过边<C,E>。同理,跳过边<C,F>.将边<B,F>加入到最小生成树结果R中。
第5步:将边<E,G>加入R中。
上一步操作之后,边<E,G>的权值最小,因此将它加入到最小生成树结果R中。
第6步:将边<A,B>加入R中。
上一步操作之后,边<F,G>的权值最小,但<F,G>会和已有的边构成回路;因此,跳过边<F,G>。同理,跳过边<B,C>.将边<A,B>加入到最小生成树结果R中。
此时,最小生成树构造完成!它包括的边依次是: <E,F> <C,D> <D,E><B,F> <E,G> <A,B>.
2、过程
2-1、存储结构
1、图的顶点
//图的顶点
char vertex[MAXSIZE];
2、图的边、克鲁斯卡尔(最小生成树数组)
//图的边(以边为主体,装入两端顶点及权)
typedef struct Edge
{int begin;int end;int weight;
}Edge;
Edge E[MAXSIZE]; //边数组
Edge K[MAXSIZE]; //Kruskal数组
2-2、从小到大排边
克鲁斯卡尔算法按照边的大小顺序,从小到大排列,需要有序的边
2-3、Kruskal算法以及防止连通(防止连通是难点)
图不能相互连通,否则那就不叫“生成树”了。
首先begin和end是两个顶点,分别在边weight的左右。
防止连通原理:一个树上的任何结点,都可以追溯到相连通的尾部,如果追溯到的尾部元素,和新添加的元素一样,那么则会产生连通,此时这两个结点不能连接。
这里设置了一个circle[]数组,为了防止连通。
追溯尾部元素的代码:
//根据顶点查找到尾(下标追溯)
int FindTail(char ch)
{int index = FindIndex(ch);while (circle[index] != -1){index = circle[index]; //追溯到尾(下标追溯)}return index; //返回尾(没有连接顶点的话返回自身)
}
Kruskal算法代码:
//克鲁斯卡尔(Kruskal)算法
//难点:是否连通判断:需要追溯到尾,如果连通的话它们有共同的尾
void Kruskal()
{int i, now = 0, tail = 0; //检测连通(头和尾)for (i = 0; i < length_e; i++) //遍历每条边{tail = FindTail(E[i].begin); //获取下标并追溯到尾(无连通则返回自身)now = FindTail(E[i].end); //获取下标并追溯到尾//未连通,正常添加if (tail != now){circle[tail] = now; //尾连通(标识连通)K[count_k].begin = E[i].begin; //左顶点K[count_k].end = E[i].end; //右顶点K[count_k].weight = E[i].weight; //中间边权printf("%c -- %d --%c\t", K[count_k].begin, K[count_k].weight, K[count_k].end);count_k++;}}
}
3、代码
//克鲁斯卡尔(Kruskal)算法
//测试案例:
/*ABCDEFG
12
12 AB
14 AG
16 AF
7 BF
9 FG
10 BC
8 EG
6 CF
2 EF
5 CE
3 CD
4 DE*/#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>#define MAXSIZE 20
#define MAX 65535
int length_v = 0; //顶点个数
int length_e = 0; //边个数
int circle[MAXSIZE] = { -1 }; //判断是否连通(里面的元素定位vertex[ ]中的元素)
int count_k; //计数克鲁斯卡尔顶点//图的顶点
char vertex[MAXSIZE];//图的边(以边为主体,装入两端顶点及权)
typedef struct Edge
{int begin;int end;int weight;
}Edge;
Edge E[MAXSIZE]; //边数组
Edge K[MAXSIZE]; //Kruskal数组//输入顶点
void InputVertex()
{int i;char ch;printf("请输入图的全部顶点(换行结束):\n");scanf("%c", &ch);for (i = 0; i < MAXSIZE && ch != '\n'; i++){vertex[i] = ch;scanf("%c", &ch);}length_v = i;
}//创建图
void CreateGraph()
{int weight = 0;char ch;printf("请输入边的数量:\n");scanf("%d", &length_e);printf("请分别输入边的权重和左右顶点:\n");for (int i = 0; i < length_e; i++) //每一条边{printf("第%d条边:\t", i + 1);scanf("%d", &weight); //权重while (weight == 32 && weight == 13) //空格判断(32为空格的ASCII码,13为回车的ASCII码)scanf("%d", &weight);E[i].weight = weight;scanf("%c", &ch); //第一个顶点while (ch == ' ')scanf("%c", &ch);E[i].begin = ch;scanf("%c", &ch); //第一个顶点while (ch == ' ')scanf("%c", &ch);E[i].end = ch;}
}//排序(按照边的权重,从低到高)
void Sort()
{int i, j, min;Edge temp;for (i = 0; i < length_e; i++){min = i;for (j = i; j < length_e; j++){if (E[min].weight > E[j].weight)min = j;}if (min != i){temp = E[min];E[min] = E[i];E[i] = temp;}}
}//根据顶点查找下标
int FindIndex(char ch)
{for (int i = 0; i < length_v; i++){if (ch == vertex[i])return i;}return -1;
}//根据顶点查找到尾(下标追溯)
int FindTail(char ch)
{int index = FindIndex(ch);while (circle[index] != -1){index = circle[index]; //追溯到尾(下标追溯)}return index; //返回尾(没有连接顶点的话返回自身)
}//循环数组初始化
void Circle_Init()
{for (int i = 0; i < length_v; i++){circle[i] = -1;}
}//克鲁斯卡尔(Kruskal)算法
//难点:是否连通判断:需要追溯到尾,如果连通的话它们有共同的尾
void Kruskal()
{int i, now = 0, tail = 0; //检测连通(头和尾)for (i = 0; i < length_e; i++) //遍历每条边{tail = FindTail(E[i].begin); //获取下标并追溯到尾(无连通则返回自身)now = FindTail(E[i].end); //获取下标并追溯到尾//未连通,正常添加if (tail != now){circle[tail] = now; //尾连通(标识连通)K[count_k].begin = E[i].begin; //左顶点K[count_k].end = E[i].end; //右顶点K[count_k].weight = E[i].weight; //中间边权printf("%c -- %d --%c\t", K[count_k].begin, K[count_k].weight, K[count_k].end);count_k++;}}
}//逐边遍历(测试输出)
void Traverse_Edge()
{int i;for (i = 0; i < length_e; i++){printf("\n第%d条边:\t 权重:%d\t 顶点1:%c\t 顶点2:%c\t ", i + 1, E[i].weight, E[i].begin, E[i].end);}
}int main()
{InputVertex(); //输入顶点CreateGraph(); //创建图Sort(); //排序Circle_Init(); //循环数组初始化printf("克鲁斯卡尔算法计算最小生成树:\n");Kruskal(); //克鲁斯卡尔(Kruskal)算法//Traverse_Edge(); //逐边遍历(测试输出)return 0;
}
参考资料
https://blog.csdn.net/guozhangjie1992/article/details/106821932?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162833230916780261971711%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162833230916780261971711&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-2-106821932.pc_search_result_control_group&utm_term=%E5%85%8B%E9%B2%81%E6%96%AF%E5%8D%A1%E5%B0%94%E8%BF%9E%E9%80%9A%E5%88%A4%E6%96%AD&spm=1018.2226.3001.4187
https://www.bilibili.com/video/BV1jW411K7yg?p=64
数据结构与算法(7-3)最小生成树(普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法)相关推荐
- 最小生成树——普里姆(Prim)算法
Prim算法的基本思想是以顶点为主导地位:从起始顶点出发,通过选择当前可用的最小权值的边把其他顶点加入到生成树中来.设连通无向网为G(V,E),在普里姆算法中,将顶点集合V分成两个子集T和T'. (1 ...
- (数据结构)图的最小生成树 普里姆算法(Prim)
假设要在n个城市之间建立通信联络网,每两个城市之间建立线路都需要花费不同大小的经费,则连通n个城市只需要n-1个条线路,最小生成树解决的问题就是:如何在最节省经费的前提下建立这个通信网 也可以理解为: ...
- 【数据结构】克鲁斯卡尔(Kruskal)算法 —PK— 普里姆(Prim)算法
目录 一.克鲁斯卡尔(Kruskal)算法 二.普里姆(Prim)算法 三.两个算法对比 求图的最小生成树的典型算法: 克鲁斯卡尔(Kruskal)算法 普里姆(Prim)算法 注:考虑问题的出发点相 ...
- 普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法
图是一种基础又重要的数据结构,图的生成树是图的一个极小连通子图.最小生成树是无向连通网的所有生成树中边的权值之和最小的一棵生成树.求图的最小生成树可以牵引出很多经典的题目,例如在N个城市之间建立通讯网 ...
- 普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法
普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法 普里姆算法的基本思想: 取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w.添加顶点w的条件为:w 和已在生成树上的顶点v ...
- 算法:通过克鲁斯卡尔(Kruskal)算法,求出图的最小生成树
之前我给大家分享过用普利姆(Prim)算法来求出图的最小生成树(点我去看看),今天我再给大家分享一个也是求图的最小生成树的克鲁斯卡尔(Kruskal)算法 克鲁斯卡尔(Kruskal)算法,就相当于先 ...
- 对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的边。说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度
对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的 边.说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度
- 【数据结构与算法】克鲁斯卡尔(Kruskal)算法
一,应用场景 公交站问题 1)某城市新增7个站点(A,B,C,D,E,F,G),现在需要修路把7个站点连通 2)各个站点的距离用边线表示(权),比如 A - B距离12公里 3)问:如何修路保证各个站 ...
- 【算法】克鲁斯卡尔 (Kruskal) 算法
目录 1.概述 2.代码实现 2.1.并查集 2.2.邻接矩阵存储图 2.3.邻接表存储图 2.4.测试代码 3.应用 本文参考: <数据结构教程>第 5 版 李春葆 主编 1.概述 (1 ...
最新文章
- 防止程序多开的两种方法
- 1、win10下连接本地系统上的Linux操作系统(分别以Nat方式和桥接模式实现)
- ★自制社交网站等级称号
- 介绍Cassandra中的压缩
- 接口安全--签名验证
- 执行一次怎么会写入两次数据_浅谈 Redis 数据持久化之 AOF 模式
- Linux之SWIG安装(无需安装pcre依赖)
- 计算机组成原理—Cache和主存的映射模式
- 【福利】本人自学深度学习的300G的学习资料愿与大家分享!一起进步!
- hihoCoder #1014 : Trie树 [ Trie ]
- C# list删除 另外list里面的元素_Java集合大全Map,Set,List
- 哈工大同义词词林扩展版-资源分享
- 工业树莓派结合USB摄像头实现远程网络监控
- 新股高中签率的技巧|提高新股中签率技巧
- 从RTP包中分析OPUS码流
- Excel的VLOOKUP函数及其用法
- 绝妙的Python语句搜集整理
- java利用iText工具包生成PDF
- jdk-7u7-linux-i586.tar.gz 和 jdk-7u51-windows-i586.exe 以及 CentOS6.8镜像下载
- NAT外网访问内网方法,内网端口映射外网ip
热门文章
- java.lang.RuntimeException: Parcelable encountered IOException writing
- 上一篇的js处理失真数据存在问题换了种方法
- [洛谷1383]高级打字机 题解
- 模拟文件上传(一):手动文件上传
- 理解GRUB2工作原理及配置选项与方法
- c语言程序设计自评报告,石家庄学院c语言程序设计自评报告.docx
- Windows 终端神器 MobaXterm,免费版可以在公司环境下使用
- ECSHOP 数据库结构说明
- python基础:python扩展包的安装方式
- 为选择屏幕的字段设置F4帮助