前言
问题描述:用克鲁斯卡尔算法求无向网图的最小生成树。本文编程软件是Visual Studio 2019,使用的是C语言进行课程设计。
提示:以下是本篇文章正文内容,下面案例可供参考。
一、目的与要求
设计目的
- 该课题的源码必须能够调试成功;
- 提供一个main函数完成的程序源码版本。基本数据结构有详细说明,每个功能函数有详细说明;
- 全文关键代码须加上注释。
设计要求
- 生成一个无向网图;
- 要求采用邻接矩阵或链接表存储来完成;
- 最后打印输出最小生成树;
- 每一个函数要有必要的注释,在课程设计中有流程图。
二、原理及方案
1.克鲁斯卡尔(Kruskal)算法
克鲁斯卡尔算法的思想是:假设G=(V,E)是一个具有n给顶点的连通网,T=(U,TE)是G的最小生成树。U的初值等于V,即拥有G中的全部顶点。算法开始时,TE的初值为空值。将图G中的边按权值从小到大的顺序依次选取,若选取的边使生成树T不形成回路,则把它并入TE中,保留作为T的一条边;若选取的边使生成树T形成回路,则将其舍弃,如此进行下去,直到TE中包含n-1条边为止,此时的T即为最小生成树。
🎈这里我们通过一个例子了解如何对一个无向带权图采用克鲁斯卡尔算法求其最小生成树:
例,对于下图所示的无向带权图,采用克鲁斯卡尔算法求最小生成树的过程为:
通过该图,可知其中的边按权值由小到大的顺序是:
从顶点5开始,连接5和4,权值为1
再连接3,4,权值为2,此时选取的边使生成树T形成回路,舍弃;从另一边连接5,0,权值为3;再连接0,2,权值为4,形成回路舍弃;连接5,1,权值为5,如下图,即得到最小生成树:
步骤 | 生成树中各顶点集合 | 生成树的边 | 生成树的权值 |
---|---|---|---|
初始状态 | {0},{1},{2},{3},{4},{5} | ||
1 | {0},{1},{2},{3},{4,5} | (4,5) | 1 |
2 | {0},{1},{2},{3,4,5} | (3,4) | 2 |
3 | {1},{2},{0,3,4,5} | (0,5) | 3 |
4 | {1},{2,0,3,4,5} | (0,2) | 4 |
5 | {1,2,0,3,4,5} | (1,5) | 5 |
实现该算法需要设一个边集合数组E,其中包括边的起始点u,终止点v和这条边的权值w。首先编写函数CreateEdge循环建立一个边集合E,并返回边的数目n。因为克鲁斯卡尔算法要求边集必须是按从小到大的顺序排好,所以编写函数seeks实现对数组E进行排序的功能,函数sort排序的结果是E中各边按从小到大排好序。
克鲁斯卡尔算法的编程思路是首先要用到辅助数组set,用来存放各顶点所在的最小生成树顶点集合,然后从边集E中顺序取出各条边,判断该边的两个顶点是否在同一集合中(需要编写一个判断顶点所在集合的函数seeks),若不在同一集合,则该边为最小生成树的一条边,输出该条边的顶点序列和权值,并在set数组中将顶点v2加到顶点v1集合中,重复以上操作直到所有顶点都在一个集合中结束。
2.方案思路
Prim算法是找点,而我们所用的Kruscal算法则是从边出发。
具体思路:
1.把所有的边和这条边代表的权值用一个数组存储起来,并按权值大小给数组排序(升序)。
2.按顺序从数组中拿出一条边,检查这条边是否与到目前为止形成的树构成环,如果形成了环,就丢弃它;如果没有,就把这条边加入树中。
3.重复步骤2,直到树中有 n-1 条边为止。
三、设计过程
程序中无向网采用邻接矩阵存储,c程序如下:
//克鲁斯卡尔(Kruscal)算法求最小生成树
#include <stdio.h>
#define MAX 100 //定义最大顶点数
typedef struct //建立一个边集合数组,其中包括边的起始点、终止点以及这条边的权值
{
int u; //一条边的起始顶点
int v; //一条边的终止顶点
int w; //边的权值(权重)
}Edge; //边的类型定义
Edge E[MAX]; //定义一个全局数组E,用于存储图的各条边(创建边的数组)
//创建一个无向网图
int CreateEdge()
{
int i;
int anum;
printf("\n输入无向网的边数:");
scanf_s("%d", &anum);
for (i = 0; i < anum; ++i)
{
printf("\n输入第%d条边的起始顶点、终止顶点以及该边的权值(v1、v2、w):\n",i+1);
scanf_s("%d %d %d", &E[i].u, &E[i].v, &E[i].w);
}
return anum;
}
//对边表进行从小到大排序算法
void sort(int n)
{
int i, j;
Edge t;
for (i = 0; i < n-1; i++)
for(j=i+1;j<n;j++)
if(E[i].w>E[j].w)
{
t = E[i];
E[i] = E[j];
E[j] = t;
}
}
//在边表中查看顶点v在哪个连通集合中
int seeks(int set[], int v)
{
int i = v;
while (set[i] > 0)
i = set[i];
return(i);
}
//克鲁斯卡尔算法的核心环节
void Kruskal(Edge E[], int n) //算法核心环节
{
int set[MAX]; //创建状态临时数组(辅助标志数组)
int v1, v2, i; //数组中的下标的临时变量
for (i = 0; i < MAX; i++)
set[i] = 0; //给set数组中的每个元素赋予初值
i = 0; //i表示特获取的生成树种的边数,初值为1
while(i<n) //按边权递增顺序,逐边检查该边是否应加入到生成树中
{
v1 = seeks(set, E[i].u); //确定顶点v所在的连通集
v2 = seeks(set, E[i].v);
if (v1!=v2) //当v1,v2不在同一顶点集合,确定该边应当选入生成树
{
printf("(%d,%d) %d\n",E[i].u,E[i].v,E[i].w);
set[v1]=v2; //将v2加入到v1的集合中
}
i++;
}
}
void main()
{
int n;
n=CreateEdge(); //调用生成边表函数
if(n>0) //判断输入的n值是否合法(n>0)
{
sort(n); //对边表集合进行排序
printf("\n最小生成树为( (顶点,顶点) 权值 ):\n");
}
else
printf("\n输入边数错误,请重新输入!\n");
Kruskal(E,n); //调用克鲁斯卡尔算法求最小生成树
}
四、设计结果
五、例题测试
刚刚更新,我们通过一道题由以上代码可解决,直接上张图:
总结
以上就是今天要讲的内容,本文利用克鲁斯卡尔(Kruscal)算法求最小生成树中(无向网采用邻接矩阵存储),最后我们要知道克鲁斯卡尔算法的时间复杂度是主要由排序方法决定的,其排序方法只与网中边的条数有关,而与网中顶点的个数无关,当使用时间复杂度为O(elog2e)的排序方法时,克鲁斯卡尔算法的时间复杂度即为O(log2e),因此当网的顶点个数较多、而边的条数较少时,使用克鲁斯卡尔算法构造最小生成树效果较好。
结语
本文参考《数据结构》–上海交大
如有错误,欢迎指正!