一,最小生成树
图1 搭建网络图
在图1中,在A到I的9个村庄中间搭建通信网络,上面的数值就是搭建网路的成本,这个时候要求实现最低的成本从而保证这9个村庄的通信。
图2 方案一的设计原理图
图3 方案二的设计原理图
图4 方案四的设计原理图
从图1到图9依次有三个方案,这个时候可以看到方案三的成本最低。但是如果结点数量很多的时候就不能使用穷举法来一一对应设计,因此这个时候怎么设计出一个快速生成最小生成树的方法是一个需要解决的问题。
二,普利姆算法
图5 普里姆算法的邻接矩阵表
图5中的原理图中,ABCDEFGH对应着v0、v2、v3、v4、v5、v6、v7、v8。
代码实现如下:
//Prim算法生成最小生成树
void MiniSpanTree_Prim(MGraph G)
{
int min, i, j, k;
int adjvex[MAXVEX]; //保存相关顶点下标[0,0,1,0,0,0,1,0,1]
int lowcost[MAXVEX]; //保存相关顶点间边的权值
lowcost[0] = 0; //v0作为最小生成树的根开始遍历,权值为0
adjvex[0] = 0; //v0第一个加入
//初始化操作
for(i=1;i<G.numVertexes; i++)
{
lowcost[i] = G.arc[0][i]; //将邻接矩阵第0行所有权值先加入数组
adjvex[i] = 0; //初始化全部先为v0的下标
}
//真正构造最小生成树的过程
for(i=1;i<G.numVertexes;i++)
{
min = INFINITY; //初始化最小权值为65535等不可能数值
j = 1;
k = 0;
//遍历全部顶点
while(j<G.numVertexes)
{
//找出lowcost数组已存储的最小权值
if(lowcost[j]!=0 && lowcost[j]<min)
{
min = lowcost[j];
k = j; //将发现的最小权值的下标存入k,以待使用
}
j++;
}
//打印当前顶点边中权值最小的边
printf("%d,%d",adjvex[k],k);
lowcost[k] = 0; //将当期顶点的权值设置为0,表示此顶点已经完成任务,进入到下一个顶点的遍历
//邻接矩阵k行逐个遍历全部顶点
for(j=1;j<G.numVertexes;j++)
{
if(lowcost[j]!=0 && G.arc[k][j]<lowcost[j])
{
lowcost[j] = G.arc[k][j];
adjvex[j] = k;
}
}
}
}
三,克鲁斯卡尔算法
无论是普里姆算法还是克鲁斯卡尔算法,他们考虑问题的出发点都是:为了使得生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能的小。普里姆算法是以某个顶点为起点,逐步找各个顶点上最小权值的边来构建最小生成树的。现在我们换一种思考方式,我们从边出发,因为权值是在边上,直接去找最小权值的边来构建生成树是自然的想法,这也是克鲁斯卡尔算法的精髓。
图6 克鲁斯卡尔算法原理图
在图6中使用的是边集数组。
//Kruskal(克鲁斯卡尔算法)生成最小生成树
int Find(int *parent, int f)
{
while(parent[f]>0)
{
f = parent[f];
}
return f;
}
void MiniSpanTree_Kruskal(MGraph, G)
{
int i, n, m;
Edge edges[MAGEDGE]; //定义边集数据
int parent[MAXVEX]; //定义parent数组用来判断边与边是否形成环路
for(i=0;i<G.numVerteses;i++)
{
parent[i] = 0;
}
for(i=0;i<G.numVerteses;i++)
{
n = Find(parent,edges[i].begin);
m = Find(parent,edges[i].end);
if(n!=m) //如果n=m,则形成环路,不满足
{
parent[n] = m; //将此边的结尾顶点放入下标为起点的parent数组中,表示此顶点已经在生成树集合中
printf("(%d,%d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
}
}
}