今天结合《大话数据结构》做一下最小生成树的知识总结。
最小生成树含义:一个图可能存在多条边,我们一定可以从图中挑出一些边连接上所以的点生成一棵树。如果我们挑选n-1条最小的边连接上n个点,付出最小的代价,从而得到最小生成树。
怎么去找边呢?那就要看下边的两种算法了。
克鲁斯卡尔(Kruskal)算法
思路:直接以边为目标去构建最小生成树,因为权值在边上,直接去找最小权值的边,但是找的过程中要注意新增加的边是否构成了环。
用到了图的存储结构中的边集数组结构。
模板的话前面的总结有写,今天的稍稍有点不同。
/*对边集数组Edge结构的定义*/
/*具体的数据类型根据题目来定,这里就用整型*/
typedef struct
{
int begin;//边的起点
int end;//边的终点
int weight;//边权,或者说边的长度
};
使得sort函数按从小到大排序
bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}
/*Find函数,找连线顶点的尾部下标*/
int Find(*parent,int x)
{
while(parent[x]>0)
x=parent[x];
return x;
}
/*Kruskal算法*/
void Kruskal_tree(MGraph G)
{
int i,n,m;//n为顶点数,m为边数
int parent[maxsize];//存储每个点的根结点,maxsize根据题目的边数来定
Edge edge[maxsize];//边集数组
sort(edge+1,edge+1+m,cmp);//将边按从小到大排序,规定边集数组是从i=1开始存储
for(int i=1;i<=n;i++)
parent[i]=i;//初始化每个结点的根结点为本身
for(int i=1;i<=m;i++)
{
int begin=Find(parent,edge[i].begin);
int end=Find(parent,edge[i].end);
if(begin!=end)//假如begin和end不相等,则证明没有此边没有和现有生成树形成环
{
parent[end]=begin;//将此边的结尾顶点end放入以下标为起点的parent中
printf("(%d,%d),%d",edge[i].begin,edge[i].end,edge[i].weight);
sum++;//已经加入到生成树的边的数量
}
if(sum==n-1)//最小生成树只有n-1条边
break;
}
}
复杂度:O(elog e)(e为边数)Find函数的复杂度是O(log e),它外面还有一个for循环。
适用的地方:边数小于点的平方较多时(稀疏图),Kruskal算法有优势。
普里姆(Prim)算法
思路:使用lowcost记录当前已遍历的顶点的权值,每一位对应该顶点在已遍历过程中出现的最小权值。lowcost初始化即任意选一顶点(一般0号顶点)作起始,将邻接矩阵图中该顶点与其它顶点的权值按序填入,该顶点本身权值为0。遍历初始化后的lowcost,找到最小权值min和对应顶点序号k。在lowcost中将已遍历的顶点对应位置0,用于过滤已遍历的顶点(起始点本身权值为0)。在邻接矩阵图中,k位顶点(第k行)按对应位将较小的权值填入lowcost。 遍历lowcost找到最小权值min和对应位置k,然后将k位置0。依此类推每次循环遍历一个顶点,直到遍历全部顶点。adjvex用于记录lowcost所记录权值的对应连线关系:顶点i与顶点adjvex[i]的权值为lowcost[i]。每次替换lowcost中较小的权值时,对应adjvex位也要变更,从而在找到lowcost最小权值时知道连线信息。通过每次(n-1次)循环打印一次连线信息,构建一个完整的最小生成树
用到了图的存储结构中的邻接矩阵
/*Prim算法*/
void Prim(MGraph G)
{
int min,i,j,k;
int adjvex[maxvex];//保存相关顶点的下标,maxvex是最大的顶点数
int lowcost[maxvex];//保存相关顶点之间的边的权值
low[0]=0;//初始化第一个顶点的到自己自身的权值,表示第一个顶点已经加入到树中
adjvex[0]=0;//初始化第一个顶点的下标为0
for(i=0;i<G.numvertexes;i++)
{
lowcost[i]=G.arc[0][i];//将与第一个顶点想连接的顶点之间的边的权值存入数组
adjvex[i]=0;//都初始化为第一个顶点的下标
}
for(i=0;i<G.numvertexes;i++)//开始最小生成树构建
{
min=inf;//inf可以初始化为不可能的大数字,表示不连通,如32767、65535
j=1;
k=0;
while(j<G.numvertexes)将全部顶点过一遍
{
if(lowcost[j]!=0&&lowcost[j]<min)//权值不为0且小于min
{
min=lowcost[j];//更行最小值
k=j;//将当前最小值的下标存入k
}
j++;
}
printf("%d %d",adjvex[k],k);当前顶点的权值最小的边
lowcost[k]=0;//表示这个点已经找到这个点连接到的边中最小的边
for(j=1;j<G.numvertexes;j++)//将全部顶点过一遍
{
if(lowcost[j]!=0&&lowcost[j]>G.arc[k][j])//如果下标为k为顶点的各边的权值小于之前这些顶点未被加入生成树的权值
{
lowcost[j]=G.arc[k][j];//存入较小权值
adjvex[j]=k;//存入下标为k的顶点
}
}
}
}
复杂度:O(n^2)
适用的地方:边数接近点的平方时(稠密图),Prim算法有优势。