Prim算法是用来干什么的? 用于生成最小生成树
那么最小生成树又是什么? 最小生成树的定义:连通图G上的一棵各边权值之和最小的带权生成树
算法思想:
假设G=(V, E)是一个具有n个顶点的连通图,其中,V是T的顶点集,E是G中边的集合。T=(U,TE)是一个图,其中,U是T的顶点集,TE是T的边集。刚开始时,U和TE的初值均为空集。
(1)首先从V中任取一个顶点,把它并入顶点集U中。
(2)然后从不属于U中的顶点,但与U中顶点直接相连的所有边中,找一条权值最小的边,将这条边并入TE中,并将相应的顶点并入U中。
(3)不断重复(2),直到U=V,TE含有n-1条边,此时的T就是G的最小生成树。
算法的详细过程
举例:假设图G如下图所示
第1步:首先,我们可以从V中取0这个顶点,将它并入顶点集U中
第2步:符合条件的边有(0, 1) (0, 2) (0, 5),而在这些边中,边的最小权值是3,于是将(0, 5)并入边集TE中,将顶点5并入顶点集U中
第3步:符合条件的边有(0, 1) (0, 2) (5, 1) (5, 2) (5, 3) (5, 4),而在这些边中,边的最小权值是1,于是将(5, 4)并入边集TE中,将顶点4并入顶点集U中
第4步:符合条件的边有(0, 1) (0, 2) (5, 1) (5, 2) (5, 3) (4, 2) (4, 3),而在这些边中,边的最小权值是2,于是将(4, 3)并入边集TE中,将顶点3并入顶点集U中
第5步:符合条件的边有(0, 1) (0, 2) (5, 1) (5, 2) (4, 2) (3, 1),而在这些边中,边的最小权值是4,于是将(0, 2)并入边集TE中,将顶点2并入顶点集U中
第6步:符合条件的边有(0, 1) (5, 1) (3, 1),而在这些边中,边的最小权值是5,于是将(5, 1)并入边集TE中,将顶点1并入顶点集U中
当操作到第6步时,U=V,TE含有n-1条边,此时红线所画的图就是图G的最小生成树
核心代码
void Prim (int n, int cost[LEN][LEN])
{
int i, j, k;
int min;
int lowcost[LEN], closest[LEN];
for (j = 1; j < n; j++)
{
lowcost[j] = cost[0][j];
//将lowcost数组中各个元素设为从顶点0到各顶点的权值
closest[j] = 0;
//依附于该边的顶点为0
}
//将顶点0作为最小生成树的第一个结点
closest[0] = -1;
//表示顶点0已经并入集合U中
//下面的整个大的for循环刚好可以输出n-1条边,以及所对应边的权值
for (i = 1; i < n; i++)
{
min = M, k = i;
for (j = 0; j < n; j++)
{
if (closest[j] != -1 && lowcost[j] < min)
{
min = lowcost[j];
k = j;
}
}
//上面的for循环是为了求出符合条件,且权值最小的边所对应的数组坐标k
printf ("(%d, %d) 权值为:%d\n", closest[k], k, lowcost[k]);
closest[k] = -1;
//将顶点k并入集合U中
for (j = 0; j < n; j++)
{
if (closest[j] != -1 && cost[k][j] < lowcost[j])
{
lowcost[j] = cost[k][j];
closest[j] = k;
}
}
//上面的for循环是为了更新两个数组中的数据
}
}
辅助操作
从上面的核心代码,我们可以知道,为了实现Prim算法,需要设置两个辅助的一维数组lowcost和closest。其中数组lowcost用来保存符合条件的边的最小权值;数组closest用来保存依附在这条边且在集合U中的顶点
步骤:
(1)假设初始状态时,我们让0作为生成树根结点的顶点,接着令closest[0]=-1,它表示顶点0已经并入集合U中;令数组lowcost中的值是顶点0和其余各顶点所构成的边的权值(注意:不与顶点0直接相连的边的权值记为M,即初始为7,4,M,M,3)
(2)输出符合条件且权值最小的边,令lowcost[k]=1,(表示顶点k已经并入集合U中)然后更新数组lowcost和closest中的数据(由于顶点k并入集合U后,符合条件的边发生了变化,所以需要根据具体情况更新数组lowcost和closest中的)
(3)重复操作(2),直到数组closest中的值都为-1时结束
生成最小生成树中两个数组的变化如下表所示,读者可自己试一遍数,可以更好地理解这个算法哦
[0] | [1] | [2] | [3] | [4] | [5] | V-U 集合 | U集合 | 生成树的边 | 边的权值 | |
---|---|---|---|---|---|---|---|---|---|---|
(1) closest | -1 | 0 | 0 | 0 | 0 | 0 | ||||
lowcost | 7 | 4 | M | M | 3 | {1, 2, 3, 4, 5} | {0} | |||
(2) closest | -1 | 5 | 0 | 5 | 5 | -1 | ||||
lowcost | 5 | 4 | 6 | 1 | 3 | {1, 2, 3, 4} | {0,5} | (0, 5) | 3 | |
(3) closest | -1 | 5 | 0 | 4 | -1 | -1 | ||||
lowcost | 5 | 4 | 2 | 1 | 3 | {1, 2, 3} | {0,5,4} | (5, 4) | 1 | |
(4) closest | -1 | 5 | 0 | -1 | -1 | -1 | ||||
lowcost | 5 | 4 | 2 | 1 | 3 | {1, 2} | {0,5,4,3} | (4, 3) | 2 | |
(5) closest | -1 | 5 | -1 | -1 | -1 | -1 | ||||
lowcost | 5 | 4 | 2 | 1 | 3 | {1} | {0,5,4,3,2} | (0, 2) | 4 | |
(5) closest | -1 | -1 | -1 | -1 | -1 | -1 | ||||
lowcost | 5 | 4 | 2 | 1 | 3 | { } | {0,5,4,3,2,1} | (1, 5) | 5 |
源代码
# include <stdio.h>
# define M 999999
# define LEN 100
int CreateTu (int cost[LEN][LEN])
{
int n, e;
//n表示图的顶点,e表示图的边
int i, j;
int v1, v2, w;
printf ("请输入该图的顶点数:");
scanf ("%d", &n);
printf ("请输入该图的边数:");
scanf ("%d", &e);
for (i = 0; i < n; i++)
{
for ( j = 0; j < n; j++)
{
cost[i][j] = M;
}
}
//初始化数组元素的值
printf ("\n请依次输入顶点,顶点,两顶点所连成边的权值!!!");
printf ("(格式:顶点 顶点 权值)\n");
for (i = 0; i < e; i++)
{
printf ("v1, v2, w = ");
scanf ("%d %d %d", &v1, &v2, &w);
cost[v1][v2] = w;
cost[v2][v1] = w;
}
return n;
//返回顶点数
}
void Prim (int n, int cost[LEN][LEN])
{
int i, j, k;
int min;
int lowcost[LEN], closest[LEN];
for (j = 1; j < n; j++)
{
lowcost[j] = cost[0][j];
//将lowcost数组中各个元素设为从顶点0到各顶点的权值
closest[j] = 0;
//依附于该边的顶点为0
}
//将顶点0作为最小生成树的第一个结点
closest[0] = -1;
//表示顶点0已经并入集合U中
//下面的整个大的for循环刚好可以输出n-1条边,以及所对应边的权值
for (i = 1; i < n; i++)
{
min = M, k = i;
for (j = 0; j < n; j++)
{
if (closest[j] != -1 && lowcost[j] < min)
{
min = lowcost[j];
k = j;
}
}
//上面的for循环是为了求出符合条件,且权值最小的边所对应的数组坐标k
printf ("(%d, %d) 权值为:%d\n", closest[k], k, lowcost[k]);
closest[k] = -1;
//将顶点k并入集合U中
for (j = 0; j < n; j++)
{
if (closest[j] != -1 && cost[k][j] < lowcost[j])
{
lowcost[j] = cost[k][j];
closest[j] = k;
}
}
//上面的for循环是为了更新两个数组中的数据
}
}
int main (void)
{
int n;
int cost[LEN][LEN];
n = CreateTu (cost);
printf ("\n最小生成树为:\n");
Prim (n, cost);
return 0;
}