用普里姆Prim构造最小生成树
一、最小生成树的概念
一个连通图的生成树是一个极小的连通子图,它含有图中的全部顶点,但只有构成一棵树的n-1条边。
对于一个带权(假定每条边上的权均大于0的数)连通无向图G中的不同生成树,其每棵树的所有边上的权值之和也可能不同。
图的所有生成树中具有边上的权值之和的树称为图的最小生成树。
ps:以上是官方说法,我们用简单易懂的语言来理解最小生成树
最小生成树:
①是一棵树(没有回路,有n个顶点,有n-1条边)
②是生成树(包含全部顶点,n-1条边都在图中)
③边的权重和最小
⭐更多通俗易懂的概念可以点击传送门目前为止感觉这个博主写的算很通俗易懂了
二、普里姆Prim算法构造最小生成树的过程
它是一种构造性算法。假设G=(V,E)是一个具有n个顶点的带权连通无向图,T=(U,TE)是G的最小生成树,其中U是T的顶点集,TE是T的边集,则由G构造从起始顶点v触发的最小生成树T的步骤如下:
(1)初始化U={v}。以v到其他顶点的所有边为侯选边。
(2)重复一下步骤n-1次,使得其他n-1个顶点被加入到U中:
①以顶点集U和顶点集V-U之间的所有边(称为割集(U,V-U))作为侯选边,从中挑选权值最小的边(称为轻边)加入TE,设该边在V-U中的顶点式k,将k加入到U中;
②考察当前V-U中的所有顶点j,修改候选边:若(k,j)的权值小于原来和顶点j关联的侯选边,则用(k,j)取代后者作为侯选边。
三、普里姆算法设计
为了便于在集合U和U-V之间选择权最小的边,建立了两个数组closest和lowest,它们记录从U到V-U具有最小权值的边,对于某个j∈V-U,closest[j]存储该边依附的在U中的顶点编号,lowcost[j]存储该边的权值
比如可以举出下图例子
可以列出lowcost和closest数组以及所选边表格如下
Prim(g,v)算法利用上述过程构造最小生成树,其中参数g为带权邻接矩阵,v为起始顶点的编号。
四、代码实现
代码实现分成两部分,一部分是图的基本算法,用于后一部分代码创建图的邻接矩阵。
①图的基本运算算法代码
#include <stdio.h>
#include <malloc.h>
#define MAXV 50 //最大顶点个数
#define MAXL 20
#define INF 0x3f3f3f3f //表示∞
//邻接矩阵的类型定义如下:
typedef struct
{ int no; //顶点编号
char data[MAXL]; //顶点其他信息
} VertexType; //顶点类型
typedef struct
{ int edges[MAXV][MAXV]; //邻接矩阵的边数组
int n,e; //顶点数,边数
VertexType vexs[MAXV]; //存放顶点信息
} MGraph; //完整的图邻接矩阵类型
//图的邻接表存储类型的定义如下:
typedef struct ANode
{ int adjvex; //该边的终点编号
int weight; //该边的权值
struct ANode *nextarc; //指向下一条边的指针
} ArcNode; //边结点类型
typedef struct Vnode
{ char data[MAXL]; //顶点其他信息
ArcNode *firstarc; //指向第一条边
} VNode; //邻接表头结点类型
typedef VNode AdjList[MAXV]; //AdjList是邻接表类型
typedef struct
{ AdjList adjlist; //邻接表
int n,e; //图中顶点数n和边数e
} ALGraph; //完整的图邻接表类型
void CreateMat(MGraph &g,int A[][MAXV],int n,int e) //建立图的邻接矩阵
{ int i,j;
g.n=n; g.e=e;
for (i=0;i<n;i++)
for (j=0;j<n;j++)
g.edges[i][j]=A[i][j];
}
void DispMat(MGraph g) //输出图的邻接矩阵
{ int i,j;
printf(" n=%d,e=%d\n",g.n,g.e);
for (i=0;i<g.n;i++)
{ for (j=0;j<g.n;j++)
if (g.edges[i][j]==INF)
printf("%4s","∞");
else
printf("%4d",g.edges[i][j]);
printf("\n");
}
}
void CreateAdj(ALGraph *&G,int A[][MAXV],int n,int e) //建立图的邻接表
{ int i,j;
ArcNode *p;
G=(ALGraph *)malloc(sizeof(ALGraph));
G->n=n; G->e=e;
for (i=0;i<n;i++)
G->adjlist[i].firstarc=NULL;
for (i=0;i<n;i++)
for (j=0;j<n;j++)
if (A[i][j]!=0 && A[i][j]!=INF)
{ p=(ArcNode *)malloc(sizeof(ArcNode));
p->adjvex=j;
p->weight=A[i][j];
p->nextarc=G->adjlist[i].firstarc;
G->adjlist[i].firstarc=p;
}
}
void DispAdj(ALGraph *G) //输出图的邻接表
{ int i;
ArcNode *p;
printf("n=%d,e=%d\n",G->n,G->e);
for (i=0;i<G->n;i++)
{ printf("[%3d]:",i);
p=G->adjlist[i].firstarc;
while (p!=NULL)
{ printf("→(%d,%d)",p->adjvex,p->weight);
p=p->nextarc;
}
printf("→∧\n");
}
}
void DestroyAdj(ALGraph *&G) //销毁图的邻接表
{ int i;
ArcNode *pre,*p;
for (i=0;i<G->n;i++)
{ pre=G->adjlist[i].firstarc;
while (pre!=NULL)
{ p=pre->nextarc;
free(pre);
pre=p;
}
}
free(G);
}
②普里姆算法求最小生成树
//普里姆算法求最小生成树
#include "Graph.cpp"
void Prim(MGraph g,int v)
{ int lowcost[MAXV];
int mincost;
int closest[MAXV],i,j,k;
for (j=0;j<g.n;j++) //给初始化lowcost和closest数组
{ lowcost[j]=g.edges[v][j];
closest[j]=v;
}
for (i=1;i<g.n;i++) //找出(n-1)个顶点
{ mincost=INF;
for (j=0;j<g.n;j++) //在(V-U)中找出离U最近的顶点k
if (lowcost[j]!=0 && lowcost[j]<mincost)
{ mincost=lowcost[j];
k=j; //k记录最近顶点的编号
}
printf(" 边(%d,%d)权为:%d\n",closest[k],k,mincost);
lowcost[k]=0; //标记k已经加入U
for (j=0;j<g.n;j++) //修改数组lowcost和closest
if (g.edges[k][j]!=0 && g.edges[k][j]<lowcost[j])
{
lowcost[j]=g.edges[k][j];
closest[j]=k;
}
}
}
int main()
{
MGraph g;
int A[][MAXV]={ //一个带权无向图
{0, 6, INF,INF,INF,1, INF},
{6, 0, 4, INF,INF,INF,3},
{INF,4, 0, 2, INF,INF,INF},
{INF,INF,2, 0, 6, INF,5},
{INF,INF,INF,6, 0, 8, 7},
{1, INF,INF,INF,8, 0, INF},
{INF,3, INF,5, 7, INF,0}};
int n=7,e=9;
CreateMat(g,A,n,e); //创建图的邻接矩阵g
printf("图的邻接矩阵:\n");
DispMat(g); //输出邻接矩阵g
printf("Prim算法结果:\n");
Prim(g,0);
return 0;
}
ps: 普里姆算法中有两重for循环,所以时间复杂度为O(n²),其中n为图中的顶点个数。
参考自《算法设计与分析》李春葆第二版