最小生成树—Prim算法详解
首先我们来解释一下什么是最小生成树:
显然这是一个带权值的图,即网结构。所谓的最小成本就是n个顶点,用(n-1)条边把一个连通图连接起来,并且使权值的和最小,我们把构造连通图的最小代价生成树称为最小生成树。
Prim算法
先把这幅图的邻接矩阵给大家:
(其实这里应该把深度优先搜索—临界矩阵的算法给出来,来求出我们需要的邻接矩阵,但是术业有专攻,我们只讲Prim,还有因为我懒,所以就直接给大家一个矩阵)
v0 v1 v2 v3 v4 v5 v6 v7 v8
v0 { 0 10 ∞ ∞ ∞ 11 ∞ ∞ ∞}
v1 { 10 0 18 ∞ ∞ ∞ 16 ∞ 12}
v2 { ∞ ∞ 0 22 ∞ ∞ ∞ ∞ 8}
v3 { ∞ ∞ 22 0 20 ∞ ∞ 16 21}
v4 { ∞ ∞ ∞ 20 0 26 ∞ 7 ∞}
v5 { 11 ∞ ∞ ∞ 26 0 17 ∞ ∞}
v6 { ∞ 16 ∞ ∞ ∞ 17 0 19 ∞}
v7 { ∞ ∞ ∞ 16 7 ∞ 19 0 ∞}
v8 { ∞ 12 8 21 ∞ ∞ ∞ ∞ 0}
话不多说,先上代码;
#define INF 65535 //代表无穷大
1.void MinispanTree-Prim(Mgraph G)
2.{
3. int min,i,j,k;
4. int low[G.vex]; //定义一个数组,存储权值
5. int vex[G.vex]; //存储顶点变化,请认真注意数组变化
6. //初始化
7. vex[0]=0; //初始化第一个权值为0,即V0加入生成树
8. low[0]=0; //初始化第一个顶点下标为0
9. //以下代码见下详解
10. for(i=1;i<G.vex;i++)
11. {
12. vex[i]=0;
13. low[i]=G.arc[0][i];
14. }
15.
16. for(i=1;i<G.vex;i++) //循环次数
17. {
18. min=INF;
19. k=0;
20. for(j=1;j<G.vex;j++)
21. {
22. if(low[j]!=0 && low[j]<min)
23. {
24. min=low[j];
25. k=j;
26. }
27. }
28. printf("(%d,%d)\n",vex[k],k);
29. low[k]=0;
30. for(j=1;j<G.vex;j++)
31. {
32. if(low[j]!=0 && G.arc[k][j]<low[j])
33. {
34. low[j]=G,arc[k][j];
35. vex[j]=k;
36. }
37. }
38. }
39.}
现在我们来把上面这段不算太长代码拆开一步一步来看:
10. for(i=1;i<G.vex;i++) //这段代码作用就是初始化,简单明了
11. {
12. vex[i]=0;
13. low[i]=G.arc[0][i];
14. }
顶点**vex[ ]**数组初始化结果
v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7 | v8 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
初始化权值数组**low[ ]**结果
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
low | 0 | 10 | ∞ | ∞ | ∞ | 11 | ∞ | ∞ | ∞ |
注意此处low数组的结果与邻接矩阵第v0行数据相同,所以我们可以明确low数组的作用是为了暂时存储权值
注意看,prim的核心算法要来了,我在这会贴出很多图
for(i=1;i<G.vex;i++) //循环次数 ,因为有9个顶点,所以我们需要循环(9-1)次,因为V0点我们默认
//已经进入了最小生成树
{
min=INF; //INF是最大值,代表无穷大
k=0;
for(j=1;j<G.vex;j++)
{
if(low[j]!=0&&low[j]<min)
{
min=low[j]; //找出权值数组中最小的权值,并且记录下标
k=j;
}
}
printf("(%d,%d)\n",vex[k],k);
................
}
我认为这段代码的作用就是体现出了“最小”二字,变量min在权值low数组中寻找最小权值,其目的就是为了找到一个最小的加入生成树。
经过遍历查找,我们发现在low数组中,最小的权值是10,其j值是1,即k=1,所以这里的输出应该是(0,1);
现在来看这段代码中的重中之重,low[k]=0表示这个顶点的权值已经加入最小生成树了,所以我们让他为0,表示已被使用;
for(i=1;i<G.vex;i++) //循环次数
{
.............
low[k]=0;
for(j=1;j<G.vex;j++)
{
if(low[j]!=0&&G.arc[k][j]<low[j])
{
low[j]=G,arc[k][j];
vex[j]=k;
}
}
}
此时我们已经找到一个顶点V1加入了最小生成树,那么下一步应该是做什么呢?
当然是找下一个邻接点了,毫无疑问,这个点的权值也应该是最小的,所以此时就应该找V1点的邻接点里的最小权值,此时的K=1,现在让我们进入这个神奇的for循环:
先来看这个if的判断条件,当权值为0时,我们就认为他已经加入了最小生成树,注意此处是low权值数组,一定要看清;在V1的邻接点的权值中,我们找到一些权值,而这些权值小于V0的邻接点权值,碰到更小的权值,我们的做法肯定是把大的踢出去啊,把小的拉进来;
v0点权值数组
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
low | 0 | 10 | ∞ | ∞ | ∞ | 11 | ∞ | ∞ | ∞ |
记得low[K]=0
V1点 | 10 | 0 | 18 | ∞ | ∞ | ∞ | 16 | ∞ | 12 |
---|
经过比较,我们发现, j=2以及j=6,j=8时,v1点邻接点的权值比v0邻接点的权值小,所以我们把v1点的小权值放入原来的low数组中,于是新的low数组就生成了:
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
low | 0 | 0 | 18 | ∞ | ∞ | 11 | 16 | ∞ | 12 |
我们到底把哪些点加入了新的权值数组中了呢?我们这时引入顶点vex[ ]数组,记录加入low权值数组的点的顶点坐标
于是vex[ ]数组变为了:
v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7 | v8 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 1 |
到这里第一次循环就结束了,上面就是prim的基本算法以及思路,接下里我将给出后两次循环的k值,low权值数组变化以及顶点vex数组的变化
第二次循环:
low数组变化
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
low | 0 | 0 | 18 | ∞ | ∞ | 11 | 16 | ∞ | 12 |
经过比较权值大小得到
K=5
记得low[K]=0
V5点 | 11 | ∞ | ∞ | ∞ | 26 | 0 | 17 | ∞ | ∞ |
---|
新的low数组
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
low | 0 | 0 | 18 | ∞ | 26 | 0 | 16 | ∞ | 12 |
vex数组
v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7 | v8 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 5 | 0 | 1 | 0 | 1 |
第三次循环:
low数组变化
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
low | 0 | 0 | 18 | ∞ | 26 | 0 | 16 | ∞ | 12 |
经过比较权值大小得到
K=8
记得low[K]=0
V8点 | ∞ | 12 | 8 | 21 | ∞ | ∞ | ∞ | ∞ | 0 |
---|
新的low数组
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
low | 0 | 0 | 8 | 21 | 26 | 0 | 16 | ∞ | 0 |
vex数组
v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7 | v8 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 8 | 8 | 5 | 0 | 1 | 0 | 1 |