Prim算法解读及其问题
Prim算法用于构造最小生成树,初始时从图中任选一顶点加入最小生成树T中,此时树中只有一个顶点,之后选择一个与当前的最小生成树T中所有的顶点集合距离最近的一个顶点,并将该顶点和相应的边加入到T中,重复此操作,最终直至图中所有顶点加入到T中,此时T为整个图的最小生成树。
算法思想:
将图中的顶点分为两个集合,一个是已经加入到树T中的集合,一个是还未加入到T中的集合,那么我们要解决的几个问题有:
①:用什么存储树T中的顶点集合以及边集合
②:如何判断未加入到树T中的顶点哪一个是距离已加入树T中的顶点集合最近的那一个点
问题解决
若是只求最小生成树的边权之和,则可以简便许多,可以设立一个visited[vexnum]数组来判断图中的顶点是否被加入到树T中,一个d[vexnum]来存储当前每个点距离集合的最短距离。
初始情况下树为空,d数组中除要加入的初始结点距离设为0外每个数都设为极大(证明无路径可通过),加入初始结点后,循环一次图中结点,若图中结点未被访问过且能通往初始结点则将d中的数值改为图中的边权值,之后的每一次加入结点都对新结点进行判断,如此即可保证d中的数值为距离树T集合中最近的点。构造完成后,d中的所有值加起来即为边权值之和。
如图
若想完整构造一棵树,则可以使用数据结构进行存储,在对d进行修改时也对node进行修改,使node存储边连接的另外一个结点,如此可根据这两个数组找到图中对应的每一条边。
typedef struct{
int *d[vexnum];
int *node[vexnum];
}
代码测试
#define Inf 100
typedef struct {
int Vex[5];
int Edge[5][5];
int vexnum, arcnum;
}Graph; //测试用矩阵
Graph initialG()
{
Graph G;
G.vexnum = 5;
G.arcnum = 6;
for (int i = 0; i < 5; i++)
{
G.Vex[i] = i;
}
G.Edge[0][1] = 2;
G.Edge[0][2] = 3;
G.Edge[0][4] = 6;
G.Edge[1][0] = 2;
G.Edge[1][3] = 5;
G.Edge[2][0] = 3;
G.Edge[2][3] = 4;
G.Edge[3][1] = 5;
G.Edge[3][2] = 4;
G.Edge[3][4] = 7;
G.Edge[4][0] = 6;
G.Edge[4][3] = 7;
return G;
}
int prim(Graph G,int v) //prim算法,用于求最小生成树的权值之和
{
int k = G.vexnum;
int visited[5]; //判定顶点是否被访问的数组
for (int i = 0; i < 5; i++)
{
visited[i] = 0; //0表示未被访问过
}
int arcs = 0; //用于存储权值之和
int d[5]; //用于存储距离集合的最短距离,将无限距离设为100
for (int i = 0; i < 5; i++)
{
if (i == v)
d[i] = 0;
else
{
d[i] = Inf; //Inf即无穷大目前无法到达
}
}
while(1)
{
int min = Inf;
int k = -1; //每次加入的顶点值
for (int j = 0; j < 5; j++)
{
if (d[j] < min && visited[j] == 0)
{
min = d[j];
k = j;
}
}
if (k != -1)
{
arcs = arcs + d[k];
visited[k] = 1; //1表示已被访问
for (int p = 0; p < 5; p++)
{
if (G.Edge[k][p] < d[p] &&G.Edge[k][p]>0&& visited[p] == 0)
d[p] = G.Edge[k][p]; //将当前距离集合最短的边筛选出来
}
}
else
{
break;
}
}
return arcs;
}
int main()
{
Graph G = initialG();
printf("最小生成树权值之和为%d", prim(G, 0));
}
测试结果