最小生成树定义
如果连通图是一个带权图,则其生成树中的边也带权,生成树中所有边的权值之和称为生成树的代价。
最小生成树(Minimum Spanning Tree) :带权连通图中代价最小的生成树称为最小生成树。
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
通俗易懂的讲就是最小生成树包含原图的所有节点而只用最少的边
和最小的权值距离
。因为n
个节点最少需要n-1
个边联通,而距离就需要采取某种策略选择恰当的边。
最小生成树的性质:
- 最小生成树是树,因此其边数等于顶点数减1,且树内一定不会有环。
- 对给定的图G(V,E),其最小生成树可以不唯一,但其边权之和一定是唯一的。
- 由于最小生成树是在无向图上生成的,因此其根结点可以是这棵树上的任意一个结点。于是,如果题目中涉及最小生成树本身的输出,为了让最小生成树唯一,一般都会直接给出根结点,只需以给出的结点作为根结点来求解最小生成树即可。
- 设G=(V,E)是一个带权连通图,U是顶点集V的一个非空子集。若u∈U ,v∈V-U,且(u, v)是U中顶点到V-U中顶点之间权值最小的边,则必存在一棵包含边(u, v)的最小生成树。
从定义上分析,最小生成树其实是一种可以看作是树的结构。而最小生成树的结构来源于图(尤其是有环情况)。通过这个图我们使用某种算法形成最小生成树的算法就可以叫做最小生成树算法。具体实现上有两种实现方法、策略分别为kruskal
算法和prim
算法。
prim
算法
从连通网N=(U,E)中找最小生成树T=(U,TE) 。
算法思想
⑴ 若从顶点v0出发构造,U={v0},TE={};
⑵ 先找权值最小的边(u,v),其中u∈U且v∈V-U,并且子图不构成环,则U= U∪{v},TE=TE∪{(u,v)} ;
⑶ 重复⑵ ,直到U=V为止。则TE中必有n-1条边, T=(U,TE)就是最小生成树。
算法实现说明
设用邻接矩阵(二维数组)表示图,两个顶点之间不存在边的权值为机内允许的最大值。
为便于算法实现,设置一个一维数组closedge[n],用来保存V- U中各顶点到U中顶点具有权值最小的边。
数组元素的类型定义是:
typedef struct
{ /* 记录从顶点集U到V-U的代价最小的边的辅助数组定义 */
VertexType adjvex;
VRType lowcost;
}minside[MAX_VERTEX_NUM];
minside closedge;
例如: closedge[j].adjvex=k,表明边(vj, vk)是V-U中顶点vj到U中权值最小的边,而顶点vk是该边所依附的U中的顶点。 closedge[j].lowcost存放该边的权值。 假设从顶点vs开始构造最小生成树。初始时令:
算法步骤
⑴ 从closedge中选择一条权值(不为0)最小的边(vk, vj) ,然后做:
① 置closedge[k].lowcost为0 ,表示vk已加入到U中。
② 根据新加入的 vk 更新 closedge 中每个元素: 对于任意的 vi∈V-U ,若cost(i, k)≦colsedge[i].lowcost,表明在U中新加 入顶点vk后, (vi, vk)成为vi到U中权值最小的边,置:
⑵ 重复 ⑴ n-1 次就得到最小生成树。
算法分析
设带权连通图有n个顶点,则算法的主要执行是二重循环:
- 求closedge中权值最小的边,频度为n-1;
- 修改closedge数组,频度为n 。
因此,整个算法的时间复杂度是 ,与边的数目无关。
算法实现
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */
#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */
#include<limits.h> //常量INT_MAX和INT_MIN分别表示最大、最小整数
/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int VRType;
typedef char InfoType;
#define MAX_NAME 3 /* 顶点字符串的最大长度+1 */
#define MAX_INFO 20 /* 相关信息字符串的最大长度+1 */
typedef char VertexType[MAX_NAME];
/* --------------------------------- 图的数组(邻接矩阵)存储表示 --------------------------------*/
#define INFINITY INT_MAX /* 用整型最大值代替∞ */
#define MAX_VERTEX_NUM 20 /* 最大顶点个数 */
typedef enum { DG, DN, AG, AN }GraphKind; /* {有向图,有向网,无向图,无向网} */
typedef struct
{
VRType adj; /* 顶点关系类型。对无权图,用1(是)或0(否)表示相邻否; */
/* 对带权图,c则为权值类型 */
InfoType *info; /* 该弧相关信息的指针(可无) */
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct
{
VertexType vexs[MAX_VERTEX_NUM]; /* 顶点向量 */
AdjMatrix arcs; /* 邻接矩阵 */
int vexnum, arcnum; /* 图的当前顶点数和弧数 */
GraphKind kind; /* 图的种类标志 */
}MGraph;
/* ---------------------------------------------------------------------------------------------*/
/* --------------------------- 需要用的图的数组(邻接矩阵)存储的基本操作 --------------------------*/
int LocateVex(MGraph G, VertexType u)
{ /* 初始条件:图G存在,u和G中顶点有相同特征 */
/* 操作结果:若G中存在顶点u,则返回该顶点在图中位置;否则返回-1 */
int i;
for (i = 0; i < G.vexnum; ++i)
if (strcmp(u, G.vexs[i]) == 0)
return i;
return -1;
}
Status CreateAN(MGraph *G)
{ /* 采用数组(邻接矩阵)表示法,构造无向网G。算法7.2 */
int i, j, k, w, IncInfo;
char s[MAX_INFO], *info;
VertexType va, vb;
printf("请输入无向网G的顶点数,边数,边是否含其它信息(是:1,否:0): ");
scanf("%d,%d,%d", &(*G).vexnum, &(*G).arcnum, &IncInfo);
printf("请输入%d个顶点的值(<%d个字符):\n", (*G).vexnum, MAX_NAME);
for (i = 0; i < (*G).vexnum; ++i) /* 构造顶点向量 */
scanf("%s", (*G).vexs[i]);
for (i = 0; i < (*G).vexnum; ++i) /* 初始化邻接矩阵 */
for (j = 0; j < (*G).vexnum; ++j)
{
(*G).arcs[i][j].adj = INFINITY; /* 网 */
(*G).arcs[i][j].info = NULL;
}
printf("请输入%d条边的顶点1 顶点2 权值(以空格作为间隔): \n", (*G).arcnum);
for (k = 0; k < (*G).arcnum; ++k)
{
scanf("%s%s%d%*c", va, vb, &w); /* %*c吃掉回车符 */
i = LocateVex(*G, va);
j = LocateVex(*G, vb);
(*G).arcs[i][j].adj = (*G).arcs[j][i].adj = w; /* 无向 */
if (IncInfo)
{
printf("请输入该边的相关信息(<%d个字符): ", MAX_INFO);
gets(s);
w = strlen(s);
if (w)
{
info = (char*)malloc((w + 1) * sizeof(char));
strcpy(info, s);
(*G).arcs[i][j].info = (*G).arcs[j][i].info = info; /* 无向 */
}
}
}
(*G).kind = AN;
return OK;
}
/* --------------------------------------------------------------------------------------------------*/
/* 实现算法7.9的程序 */
typedef struct
{ /* 记录从顶点集U到V-U的代价最小的边的辅助数组定义 */
VertexType adjvex;
VRType lowcost;
}minside[MAX_VERTEX_NUM];
int minimum(minside SZ, MGraph G)
{ /* 求closedge.lowcost的最小正值 */
int i = 0, j, k, min;
while (!SZ[i].lowcost)
i++;
min = SZ[i].lowcost; /* 第一个不为0的值 */
k = i;
for (j = i + 1; j < G.vexnum; j++)
if (SZ[j].lowcost > 0)
if (min > SZ[j].lowcost)
{
min = SZ[j].lowcost;
k = j;
}
return k;
}
void MiniSpanTree_PRIM(MGraph G, VertexType u)
{ /* 用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边 算法7.9 */
int i, j, k;
minside closedge;
k = LocateVex(G, u);
for (j = 0; j < G.vexnum; ++j) /* 辅助数组初始化 */
{
if (j != k)
{
strcpy(closedge[j].adjvex, u);
closedge[j].lowcost = G.arcs[k][j].adj;
}
}
closedge[k].lowcost = 0; /* 初始,U={u} */
printf("最小代价生成树的各条边为:\n");
for (i = 1; i < G.vexnum; ++i)
{ /* 选择其余G.vexnum-1个顶点 */
k = minimum(closedge, G); /* 求出T的下一个结点:第K顶点 */
printf("(%s-%s)\n", 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集后重新选择最小边 */
strcpy(closedge[j].adjvex, G.vexs[k]);
closedge[j].lowcost = G.arcs[k][j].adj;
}
}
}
void main()
{
MGraph G;
CreateAN(&G);
MiniSpanTree_PRIM(G, G.vexs[0]);
}
运行结果:
输入的无向图:
程序运行过程图示: