5.4.1图的应用(最小生成树)
连通图的生成树:极小的连通子图。包含图中全部的顶点,但只有足以构成一棵树的n-1条边。
最小生成树:总权值最小的的生成树。
最小生成树的求解算法:
普利姆算法
算法思想:
需要两个数组: lowcost[n] , adjvex (n是图中的顶点数)
1)从图中找第一个起始顶点V0,作为生成树的第一个顶点,然后从这个顶点到其他所有边中挑选一条权值最小的边。然后与把这条边的另一个顶点也加入到生成树中。
2)对剩余其他的所有顶点,分别检查这些顶点与顶点v的权值是否比这些顶点在lowcost数组中对应的权值小,如果更小,则用较小的权值更新lowcost数组。
3)从更新后的lowcost数组中继续挑选权值最小而且不在生成树中边,然后加入到生成树中。
4)反复执行2)3)直到所有顶点都加入到生成树中。
Void MiniSpanTree_Prim(MGraph G) //用邻接矩阵存的图
{
int min,i,j,k;
int adjvex[MAXVEX];
int lowcost[MAXVEX];
/*初始化*/
lowcost[0]=0; // 下边为0的顶点加入到生成树中
adjvex[0]=0;
for(i=1 ; i<G.vexnum; i++) //出下边为0的所有顶点
{
lowcost[i]=G[0][i]; //将与下标为0的顶点有边权值存入loccost数组
adjvex[i]=0; // 这些顶点的adjvex数组全部初始化为0
}
/*算法核心*/
for(i=1; i<G.vexnum; i++) //只需要循环N-1次
{
min =65535;
j=0;
k=0;
/*找顶点*/
//找出lowcost中最小的 最小的权值给min 下标给k
while(j<G.vexnum) //从一号开始找
{
if(lowcost[j]!=0&&lowcost[j]<min)//不在生成树中的顶点而且权值更小的的
{
min =lowcost[j]; //更新权值
k=j; //找到了新的点下标给k
}
j++; // 在看下一个顶点
}
printf("%d->%d",adjvex[k],k);
lowcost[k]=0; // 将这个顶点加入生成树
//生成树加入新的顶点,熊下标为1的顶点开始更新lowcost数组值
for(j=0; j<G.vexnum; j++)
{
if(lowcst[j]!=0; &&G.arc[k][j]<lowcost[j])
{
lowcost[j]=G.arc[k][j];
adjvex[j]=k; //修改这条边邻接的顶点
}
}
}
}
举例:
算法分析:
双重循环,外层循环次数为n-1,内层并列两个循环次数都是n。故普利姆算法时间复杂度为O(n^2),而且时间复杂度只和n有关系 ,所以适合稠密图(顶点相对于边来说少)。
克鲁斯卡尔算法:
生成树包含n个顶点,n-1条边。
算法思路:
将图中边按权值从小到大排列,然后从最小的边开始扫描,设置一个边的集合来记录,如果该边并入不构成回路的话,则将该边并入当前生成树。直到所有的边都检测完为止。
排序:堆排序
不构成回路:并查集
并查集:
合并:一个集合的根结点为另一个集合根结点的孩子
查:两个节点的根结点相同,那它们是联通的,为同一集合中的,相连就会产生回路。
//查找某个集合的根结点:
int Find(int *parent,int x){
while(parent[x]>=0)
x=parent[x];
return x;
}
//合并两个集合
void Union(int *parent,int root1,int root2){
parent[root2]=root1;
}
克鲁斯卡尔代码:
#define MaxSize 100
typedef struct{
int a,b; //变得两个顶点
int weight; //边的权值
}Edge; //边结构体
int Find(int *parent, int x){
while(parent[x]>0)
x=parent[x];
return x;
}
Edge edges[MaxEdge]; // 边数组
int parent[MaxVex]; // 父亲顶点数组(并查集)
int MiniSpanTree_Kruskal(MGraph G){
int i, n,m;
sort(edges); //按权值从小到大排序
for(i=0;i<G.vexnum;i++){
parent[i] =-1; //初始化
}
for(i=0;i<G.arcnum;i++){ //扫描每条边
n=Find(parent,edges[i].a);// n是这条边第一个顶点的根顶点所在的下标
m=Find(parent,edges[i].b); //n是这条边第二个顶点的根顶点所在的下标
if(n!=m){
parent(n)=m;//并操作
printf("(%d->%d)",deges[i].a,deges[i].a)
}
}
}
边数组:
初始化的父亲顶点数组:
第一次:边为1 - 4 n=1 m=4 n!=m parent[1]=4 打印 1- 4
第二次:边为5 -6 n=5 m=6 n!=m parent[5]=6 打印 5-6
第三次:边为4-5 n=4 m=6 n!=m parent[4]=6 打印 4-5
第四次:边为0-1 n=0 m=6 n!=m parent[0]=6 打印0-1
第五次:边为1-5 n=6 m=6 n==m
第六次:边为0-2 n=6 m=2 n!=m parent[6]=2 打印0-2
第七次:边为1-3 n=2 m=3 n!=m parent[2]=3 打印0-1
已经够六条边够了。
算法分析:
克鲁斯卡尔算法操作分为对边权值排序部分和一个单重for循环,它们是并列关系,由于排序耗费时间大于单重循环,所以主要用时为排序上。排序和图中的边的数量有关系,所以适合稀疏图。
最小生成树: