一,最小生成树
首先得了解最小生成树是啥意思,就是在下面的图中,去掉一些边之后,还可以从一个顶点到达另一个顶点,不用考虑距离。
把顶点想象成一个个村庄,把边想象一条条成路,而要是所有的村庄之间接电话线。。。哈哈哈哈,然后使所选的路径最短,这个就是最小生成树了。
二,Prim算法
在这个算法中我的图是用邻接矩阵的方式存储的。
我们需要构造两个辅助数组才能实现这个算法。
lowcost数组用来表示当前顶点已经进入了最小生成树中去同时还记录着最小的边。
adjvex数组则是用来存储于上个数组的最小边所对应的顶点。
看文字可能有点绕,在走图的过程中就明白了。
第一步:首先将第一个顶点加入到生成树中去,同时将自身的边录入进去
int i,j;
int lowcost[MAX_VEX]; //最低成本 --- 表示最小生成树
int adjvex[MAX_VEX]; //这里面存放都是 来自最低成本所对应的顶点
//第一个顶点进去最小生成树中去
lowcost[0] = 0;
adjvex[0] = 0;
//将第一个顶点所对应的边录入
for (i = 1; i < G.vexnum; i++)
{
lowcost[i] = G.arcs[0][i];
adjvex[i] = 0; //因为刚开始lowcost里面的边全是第一个顶点的
}
第二步:找到当前的生成树数组中最小的边,并对其进行记录以及其下标。
这一步就是在遍历数组而已,要注意的就是那个条件是 未加入生成树的
//去遍历所有顶点
for ( i = 1; i < G.vexnum; i++)
{
int min = MAX_NUM,k;
//找当前权值最小的边
for (j = 1; j < G.vexnum; j++)
{
//找最小,用k记录其下标
if(lowcost[j] < min && lowcost[j] != 0)
{
min = lowcost[j];
k = j;
}
}
//.....................
}
第三步: 确定这条边了,同时记录为0表示任务已经完成
printf("(%d,%d) 权值为:%d \n",adjvex[k],k,min);
lowcost[k] = 0; //记录为0 表示该节点已经进入了最小生成树中去了
第四步:将新加进来的顶点,所对应的边录入到lowcost数组中去。
//将新进入最小生成树的顶点的边(小)录入数组
for (j = 1; j < G.vexnum; j++)
{
if(lowcost[j] != 0 && G.arcs[k][j] < lowcost[j])
{
lowcost[j] = G.arcs[k][j];
adjvex[j] = k;
}
}
接下来的顶点操作全部一样。最终结果如下:
三,Kruskal算法
就比较上面的Prim算法来讲,其实这个Kruskal算法的核心更少,但不同的是Kruskal算法是采用了边集数组的存储结构。
并且得对边集数组进行排序,如果数组元素多,也就是边多,就又得考虑实现一个好的排序算法了,所以我在这里顶点少,边也少,采用的是方便的冒泡排序算法。
第一步:构造一个边集数组,判断是否回路的数组parent。
这里只有写算法的思想,如何构造边集数组和排序在结尾出源码中有。
int parent[MAX_VEX]; //判断是否回路
int i;
//构造边集数组
Edge edges[MAX_ARC];
EdgeCreate(edges, G);
//给边集数组排序
Esort(edges, G.arcnum);
//初始化回路数组全为0
for (i = 0; i < MAX_VEX; i++)
{
parent[i] = 0;
}
看边集数组,你的权值又是拍好的数据,直接打印不就好了么,确实是这样的,但是直接打印可能出现回路。所以接下来的第二步。
第二步:判断是否回路
int Find(int* p,int f)
{
//初始化为 0 证明是 0就没有录入
while(p[f] > 0)
{
//去一直找它的上一级
f = p[f];
}
return f;
}
//克鲁斯卡尔 -- Kuskal算法
void MinSpanTreeKuskal(AMGraph G)
{
int parent[MAX_VEX]; //判断是否回路
int i;
//构造边集数组
Edge edges[MAX_ARC];
EdgeCreate(edges, G);
//给边集数组排序
Esort(edges, G.arcnum);
//初始化回路数组全为0
for (i = 0; i < MAX_VEX; i++)
{
parent[i] = 0;
}
printf("Kruskal最小生成树为:\n");
//遍历顶点
for(i = 0; i < G.arcnum; i++)
{
int n = Find(parent,edges[i].begin);
int m = Find(parent,edges[i].end);
if(n != m)
{
parent[n] = m; //将此边的尾顶点录入数组
//表示此顶点已经在生成树中了
printf("(%d,%d) %d \n",edges[i].begin,edges[i].end,edges[i].wight);
}
}
}
而整个Kruskal最核心的东西我感觉也就是这个了。
四,总结
我个人感觉,Prim算法虽然写起来多,但是好理解,比这个Kruskal的parent判断回路好理解。而Kruskal算法核心就是这个判断回路🤣🤣🤣。
Prim和Kruskal算法源码