目录
一、Kruskal 算法简介
1.1 Kruskal 算法思想
Kruskal(克鲁斯卡尔)算法是一种用来在加权连通图中寻找最小生成树的算法,其操作对象是边。
Kruskal 思想:
1. 从加权图中找出所有的边;初始时,所有边都不属于最小生成树,最小生成树为。
2. 从不属于最小生成树的边中找到权值最小的边,判断最小边及其连接的两个顶点加入到最小生成树是否会形成环路。
a)若不形成环路,则将此最小边及其连接的顶点并入最小生成树;
b)若形成环路,则永远不再看此边,然后从剩下的且不属于最小生成树的边中,寻找权值最小的边。
3. 重复上述步骤,直至所有顶点均连接在一起,并没有形成环路时,最小生成树就找到了。
1.2 Kruskal 过程图解
举例:
1.3 Kruskal 两个核心问题
问题1 如何寻找权值最小的边
应对策略:对图的所有边按照权值大小进行排序,然后按从小到大的顺序取出边。
*在这里,通常定义一种存储边及其连接的顶点的结构体数组。
问题2 如何判断边及其顶点加入最小生成树是否会形成回路。
应对策略:每条边机及其相连的顶点都视作一颗子树,然后判断这课子树的根是否和最小生成树的根相同;若相同,则会形成回路;若不同,则不会形成回路,将子树并入最小生成树。
1.4 适用场景(Kruskal 与 Prime 的比较)
1. Kruskal 算法 是将边作为操作对象,当加权图的边越多,要处理的边也越多,则算法的时间复杂度就越高;而顶点的数量对算法的时间复杂度无影响。所以,Kruskal 算法适合处理稀疏图(边较少的图)。时间复杂度:O(),e为边的个数。
2. Prime 算法 是将顶点作为操作对象,当加权图的顶点越多,则算法的时间复杂度就越高;而边的数量对算法的时间复杂度无影响。所以,Prime 算法适合处理稠密图(边较多的图)。时间复杂度:O()
二、Kruskal 的实现
2.1 头文件
#include<stdio.h>
// 顶点的最大个数
#define MaxVertix 30
#define INF 32767 // INF infinite 无穷大,表权重无穷大
// 状态值
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
// 状态码 -- 状态值
typedef int Status;
// 1.邻接矩阵 - 存储结构
// 定点、边的类型
typedef char VertexType; // 顶点的数据类型
typedef int ArcType; // 边的数据类型
// 构造数据类型
typedef struct
{
VertexType Verts[MaxVertix];
ArcType UdArcs[MaxVertix][MaxVertix]; // 无向图 -- 矩阵表示法
int VerNum; // 顶点个数
int ArcNum; // 边的个数
}AMGraph; // Adjacency Matrix Graph 邻接矩阵
// 狐结点类型 - 用狐表示的图结构
typedef struct
{
int v1, v2; // 狐关联的两顶点下标
int Weight; // 狐的权重
}Road;
// 函数声明
Status CreateUDN(AMGraph* G); // 创建无向图
//最小生成树的 Kruskal 克鲁斯卡尔 算法
Status CreateGTree_Kruskal(AMGraph* G); // 根据邻接矩阵图,从第 v 个结点构造 最小生成树
2.2 函数文件
#include "Graph.h"
// 获取 顶点 ver 的下标
int LocateVertex(AMGraph* G, VertexType* v)
{
if (!G) return ERROR;
int i;
for (i = 0; i < G->VerNum; i++)
if (*v == G->Verts[i])
return i;
return -1; // 返回 -1 表示未找到顶点
}
// 创建无向图
Status CreateUDN(AMGraph* G) // UndirectNet
{
if (!G) return ERROR;
printf("请输入顶点及边个数(Vers Arcs): ");
scanf("%d %d", &G->VerNum,&G->ArcNum);
getchar();
int i;
//录入顶点
printf("\n请输入顶点的值(英文字母): ");
for (i = 0; i < G->VerNum; i++)
{
do
{
VertexType v;
scanf("%c", &v); //scanf("%[a-zA-Z]", G->VerNum); // 只接收26个英文字母
getchar();
if ((65 <= v && v <= 90) || (97 <= v && v <= 122))
{
G->Verts[i] = v;
break;
}
printf("输入错误,请输入英文字母!\n");
} while (1); // do-while循环用于处理错误输入
}
//初始化所有边的权
int j;
for(i = 0; i < G->VerNum; i++)
for (j = i; j < G->VerNum; j++)
{
G->UdArcs[i][j] = INF; // 权重为无穷大,表示两顶点非邻接
G->UdArcs[j][i] = INF;
}
//录入边的权值
printf("\n请输入边关联的顶点及权值(v1 v2 weight): \n");
for (i = 0; i < G->ArcNum; i++)
{
VertexType v1, v2;
int w;
do
{
scanf("%c %c %d", &v1, &v2, &w);
getchar();
if (v1 < 65 || (90 < v1 && v1 < 97) || 122 < v1)
{
printf("输入错误,请输入英文字母!\n");
continue;
}
if (v2 < 65 || (90 < v2 && v2 < 97) || 122 < v2)
{
printf("输入错误,请输入英文字母!\n");
continue;
}
//查找顶点位置
int a, b;
a = LocateVertex(G, &v1);
b = LocateVertex(G, &v2);
if (a < 0) // 判断顶点是否存在
{
printf("输入的顶点%c不存在,请重新输入!\n",v1);
continue;
}
if (b < 0) // 判断顶点是否存在
{
printf("输入的顶点%c不存在,请重新输入!\n", v2);
continue;
}
//链接到两顶点的边赋权值
G->UdArcs[a][b] = w;
G->UdArcs[b][a] = w;
break;
} while (1); // do-while循环用于处理错误输入
}
return OK;
}
// 将狐按从小到大排序
Status Sort(Road R[], int e)
{
if (!R) return ERROR; // 处理空数组
int i, j;
// 冒泡排序
for(i = 0; i < e - 1; i++) // e 个数字,排 e - 1 趟。排完 1 趟 i+1
for (j = 1; j < e - 1 - i; j++) // 数字之间的大小关系所需要比较的次数。每排完一趟,比较次数-1
if (R[j - 1].Weight > R[j].Weight) // 前一个数大于后面一个数,交换两数的位置
{
Road tmp = R[j - 1];
R[j - 1] = R[j];
R[j] = tmp;
}
return OK;
}
//获取结点所在子树的根
int GetRoot(int r[], int len, int v) // len - 数组长度; v - 结点下标
{
if (!r) return -1; // 处理空数组
int i;
for (i = 0; i < len; i++)
{
if (r[v] == v) // 一个顶点存储的是自己的下标,则表示此顶点是一颗树的根结点
return v;
else v = r[v];
}
return -1;
}
// 根据邻接矩阵图,从第 v 个结点构造 最小生成树
Status CreateGTree_Kruskal(AMGraph* G)
{
/* 思路:以狐为单位,通过选取最小的狐来构造一颗颗局部的小树(小树也是最小生成树),
再将小树合并为一个大树,就得到完整的最小生成树(采用了局部最优得到整体最优的思想)
步骤:
1.选取最小的狐
2.判断最小狐关联的两顶点是否属于同一颗树
a.若最小狐关联的两顶点属于不同的两颗树,就将这条狐和这两颗树合并到一颗树里面
b.若最小狐关联的两顶点属于同一颗树,舍弃这条狐(两顶点都在一颗树里面了,还并入这条狐,就出现回路了,就不是树结构了)
3.重复上述步骤,直至得到一个完整的最小生成树 */
//处理空指针、非法下标
if (!G)
return ERROR;
Road road[MaxVertix]; // 记录狐关联的顶点。通过狐来表示出图中顶点与顶点、顶点与狐、狐与狐之间的关系
// 记录每颗树的根结点下标,用于判断狐关联的两顶点是否属于同一棵树,防止回路
// 数组下标对应顶点,数组存储的值是顶点所属树的根的下标
int root[MaxVertix];
int i,j,k = 0;
for (i = 0; i < G->VerNum; i++)
{
root[i] = i; //初始化:将每个顶点视作独立的一颗树
// 寻找连通顶点的下标、狐的权重,并记录下来
if(k < G->ArcNum)
for (j = i+1; j < G->VerNum; j++) // 无向图的邻接矩阵是对称的,所以只有统计上三角或者下三角即可
if (G->UdArcs[i][j] < INF) // 两顶点连通
{
road[k].v1 = i; // 记住顶点1的小标
road[k].v2 = j; // 记住顶点2的小标
road[k++].Weight = G->UdArcs[i][j]; // 记住两连通顶点关联的狐的权重
}
}
Sort(road, G->ArcNum); // 将狐按从小到大的顺序排序
//寻找最小生成树
for (i = 0; i < G->ArcNum; i++) // 已是升序,每次都能去到最小的狐
{
int a = GetRoot(root,G->VerNum,road[i].v1); // 获取结点所在树的根
int b = GetRoot(root,G->VerNum,road[i].v2);
if (a != b) // 根结点不相同,则两结点不属于同一颗树。
{
root[a] = b;// 合并两棵树。将一颗树的根结点作为另一颗树的根(2棵树拥有同一个根时,就合二为一了)
printf("%c--(%d)-->%c\n", G->Verts[road[i].v1], road[i].Weight, G->Verts[road[i].v2]);
}
}
return OK;
}
GetRoot 函数的理解:
整个算法执行的过程相当于是将多棵树拼装成一棵树,GetRoot 函数中的 for 循环相当于是寻找每棵子树的根结点,也就是说返回的 n 和 m 的值其实就相当于两棵子树的根节点,如果根结点相同,肯定就是同一棵树,即图连通了,出现了环路;如果不相等,就将两棵子树拼装成一棵树,即 root[a] = b。
root 数组中下标 v 位置的值为 v,即 root[v] == v,表示找到根结点了。
root 数组的理解:
root[a] = b,表示:将根结点为 b 的子树,并入根结点为 a 的子树,即两棵子树合并为一棵子树。
起初,root[i] = i ,表示:将每个顶点视作独立的一棵子树,即每个顶点都是一棵独立的树。
root[0] = 1,表示:下标为 0 的结点的前驱结点为 下标为 1 的结点
2.3 主函数(数据测试)
#include "Graph.h" // 引用头文件
int main()
{
AMGraph amg;
CreateUDN(&amg);
int i, j;
//打印邻接矩阵
printf("\n图的邻接矩阵: \n ");
for (i = 0; i < amg.VerNum; i++) // 打印列标
printf("%-5c ", amg.Verts[i]);
for (i = 0; i < amg.VerNum; i++)
{
//printf("%c ", 'A' + i);
for (j = 0; j < amg.VerNum; j++)
{
if (j > 0) printf(" ");
else printf("\n%c ", amg.Verts[i]); // 打印行标
printf("%-5d ", amg.UdArcs[i][j]); // 矩阵值
}
}
//寻找最小生成树
printf("\n\n最小生成树如下:\n");
CreateGTree_Kruskal(&amg);
return 0;
}
/* 测试数据 及 结果
__ A __
1/ | \
/ 2| \3
B—-7—-C—-6—-D
\ 4/ 5/
8 \ / /
E—-9—-F
请输入顶点及边个数(Vers Arcs): 6 9
请输入顶点的值(英文字母): A B C D E F
请输入边关联的顶点及权值(v1 v2 weight):
A B 1
A C 2
A D 3
B C 7
B E 8
C D 6
C E 4
D F 5
E F 9
图的邻接矩阵:
A B C D E F
A 32767 1 2 3 32767 32767
B 1 32767 7 32767 8 32767
C 2 7 32767 6 4 32767
D 3 32767 6 32767 32767 5
E 32767 8 4 32767 32767 9
F 32767 32767 32767 5 9 32767
最小生成树如下:
A--(1)-->B
A--(2)-->C
A--(3)-->D
C--(4)-->E
D--(5)-->F
*/
em ~ ~ ~ ~ ~
在此,感谢您的倾心阅读!
在茫茫人海中,你我能够相遇属实是一件难得的事!
以上是本人的一些浅显理解,如有不妥之处,还望指出,咋们共同进步哟!