目录
一、普利姆算法的实现定义
假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:
在所有u∈U,v∈(V-U)的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。
此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。
二、代码讲解
无向图以邻接矩阵的形式存储:
该邻接矩阵中除了边外的所有值都被记为无限INFINITY,例如:假设V1、V2间无边,矩阵中(V1,V2)会被记为INFINITY(这是显然的),而(V1,V1)和(V2,V2)也会被记为INFINITY。
typedef char VertexType; // 顶点类型
typedef int EdgeType; // 边上的权值数据类型
#define MAXVEX 30 // 最大顶点数
#define INFINITY 65535 // 无限,边矩阵中填入INIFINITY表示两顶点间无边
typedef struct Graph
{
VertexType vertex[MAXVEX]; // 顶点数组
EdgeType arc[MAXVEX][MAXVEX]; // 边数组,数组中的值为权
int numVertex, numEdge; // 当前顶点数和边数
}Graph;
算法如下:
int LengthVex(int vex[]) // 返回当前vex数组的长度
{
int i, num=0;
for(i=0; i<MAXVEX; i++)
{
if(vex[i] != INFINITY)
num++;
}
return num;
}
int FindInVex(int vex[], int e) // 查看vex数组中是否有元素e,有则返回1,无则返回0
{
int i;
for(i=0; i<MAXVEX; i++)
{
if(vex[i] == e)
return 1;
}
return 0;
}
void MiniSpanTree_Prim(Graph *G) // Prim算法最小生成树
{
int minEdge; // 记录权值最小的边
int vex[MAXVEX]; // 记录已连入生成树的顶点下标,数组下标表示先后顺序
int i, j, k=0;
int vexBegin, vexEnd; // 边起点和边终点
for(i=0; i<MAXVEX; i++) // 将数组中的值全部初始化为无限
vex[i] = INFINITY;
vex[0] = 0; // 从顶点v0开始生成树,也可给函数增加一个形参以从指定顶点开始生成树
while(k < LengthVex(vex))
{
minEdge = INFINITY;
for(i=0; i < LengthVex(vex); i++)
for(j=0; j < G->numVertex; j++)
if(G->arc[ vex[i] ][j] < minEdge && !FindInVex(vex, j) )
// 找还未被并入最小生成树的权值最小边
{
minEdge = G->arc[ vex[i] ][j]; // 记录权值
vexBegin = vex[i]; // 记下边的起点和终点
vexEnd = j;
}
k++;
if(minEdge != INFINITY)
{
vex[k] = vexEnd; // 新顶点加入生成树
printf("edge:(%d, %d) weight:%d \n", vexBegin, vexEnd, minEdge);
}
}
}
代码参考了《大话数据结构》,但个人认为书中代码使用的一维数组adjvex[]不太直观也略微有些难以理解,所以做了一些改变。对初学者来说应该会容易理解一点吧?大概。
代码中的vex[]数组用于记录已经连入最小生成树,或者说已经从集合(U-V)中并入集合U的顶点,vex[]数组中的值就是这些顶点的下标。
vexBegin和vexEnd仅用于在循环过程中记下权值最小边的起始顶点和结束顶点,在循环结束后打印输出。虽然定义了vexBegin和vexEnd,但图仍然是无向图哦。
算法的核心部分:
while(k < LengthVex(vex))
{
minEdge = INFINITY; // 在上次循环中minEdge可能记下了一个最小权值,
// 所以要在新循环开始时重新将其初始化为无限
for(i=0; i < LengthVex(vex); i++)
for(j=0; j < G->numVertex; j++)
if(G->arc[ vex[i] ][j] < minEdge && !FindInVex(vex, j) )
// 找还未被并入最小生成树的权值最小边
{
minEdge = G->arc[ vex[i] ][j]; // 记录权值
vexBegin = vex[i]; // 记下边的起点和终点
vexEnd = j;
}
k++; // 与LengthVex(vex)配合在合适的时候结束while循环
// minEdge==INFINITY表示所有能连入生成树的顶点都已经连入,总体while循环应该结束
// 当minEdge==INFINITY时数组vex[]不再有新元素加入,LengthVex(vex)值不再增加
// 此时while循环条件不再满足,循环终止
if(minEdge != INFINITY)
{
vex[k] = vexEnd; // 新顶点加入生成树,LengthVex(vex)值会相应增加
printf("edge:(%d, %d) weight:%d \n", vexBegin, vexEnd, minEdge);
}
}
这一部分代码是对本文开头Prim算法实现定义的直观构造,数组vex[]即定义中的集合U,嵌套的for()循环就是 不断寻找起点在集合U,终点在集合(U-V)的最小权值边,然后将该边所连接的新顶点并入集合U的过程。
在这一过程中U从{V0}开始不断有新顶点并入,直到图中的所有顶点都被并入U。每当有一个顶点被并入U,就有一条边被选中,U从{V0}开始有n-1个顶点并入,则也有n-1条边被选中,被并入边集合TE(当然代码中没有边被并入的直观表示)。
三、总体代码及运行实例
#include<stdio.h>
#include<stdlib.h>
typedef char VertexType; // 顶点类型
typedef int EdgeType; // 边上的权值数据类型
#define MAXVEX 30 // 最大顶点数
#define INFINITY 65535 // 无限,边矩阵中填入INIFINITY表示两顶点间无边
typedef struct Graph
{
VertexType vertex[MAXVEX]; // 顶点数组
EdgeType arc[MAXVEX][MAXVEX]; // 边数组,数组中的值为权
int numVertex, numEdge; // 当前顶点数和边数
}Graph;
void CreateGraph(Graph *G) // 创建无向图的邻接矩阵表示
{
int i,j,k,w;
printf("依次输入顶点数和边数:(数值间用“,”分隔)\n");
scanf("%d,%d", &(G->numVertex), &(G->numEdge));
getchar(); // 由于scanf()从缓冲区读取字符,
// 需要用getchar()吃掉上一个scanf()末尾键入的回车
printf("依次输入各顶点:\n");
for(i=0; i < G->numVertex; i++)
scanf("%c", &(G->vertex[i]));
for(i=0; i < G->numVertex; i++) // 边矩阵中的值全部初始化为无限,表示图中不存在边
for(j=0; j < G->numVertex; j++)
G->arc[i][j] = INFINITY;
for(k=0; k < G->numEdge; k++) // 向边矩阵中填入边的权值
{
printf("输入边(vi,vj)的下标i,j与边的权w:(数值间用“,”分隔)\n");
scanf("%d,%d,%d", &i, &j, &w);
G->arc[i][j] = w;
G->arc[j][i] = G->arc[i][j]; // 无向图的边矩阵为对称矩阵
}
}
int LengthVex(int vex[]) // 返回当前vex数组的长度
{
int i, num=0;
for(i=0; i<MAXVEX; i++)
{
if(vex[i] != INFINITY)
num++;
}
return num;
}
int FindInVex(int vex[], int e) // 查看vex数组中是否有元素e,有则返回1,无则返回0
{
int i;
for(i=0; i<MAXVEX; i++)
{
if(vex[i] == e)
return 1;
}
return 0;
}
void MiniSpanTree_Prim(Graph *G) // Prim算法最小生成树
{
int minEdge; // 记录权值最小的边
int vex[MAXVEX]; // 记录已连入生成树的顶点下标,数组下标表示先后顺序
int i, j, k=0;
int vexBegin, vexEnd; // 边起点和边终点
for(i=0; i<MAXVEX; i++) // 将数组中的值全部初始化为无限
vex[i] = INFINITY;
vex[0] = 0; // 从顶点v0开始生成树
while(k < LengthVex(vex))
{
minEdge = INFINITY;
for(i=0; i < LengthVex(vex); i++)
for(j=0; j < G->numVertex; j++)
if(G->arc[ vex[i] ][j] < minEdge && !FindInVex(vex, j) )
// 找还未被并入最小生成树的权值最小边
{
minEdge = G->arc[ vex[i] ][j]; // 记录权值
vexBegin = vex[i]; // 记下边的起点和终点
vexEnd = j;
}
k++;
if(minEdge != INFINITY)
{
vex[k] = vexEnd; // 新顶点加入生成树
printf("edge:(%d, %d) weight:%d \n", vexBegin, vexEnd, minEdge);
}
}
}
int main()
{
Graph *G = (Graph*)malloc(sizeof(Graph));
CreateGraph(G);
MiniSpanTree_Prim(G);
}
输入示例及运行结果:
输入的图为:
该图的最小生成树应为:
运行结果(前面为构造图的输入,后面为最小生成树的输出结果):
显然运行结果符合最小生成树,程序正确运行。