最小生成树问题-Kruskal方法求解
先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可 取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。
时间复杂度为为O(e^2), 使用并查集优化后复杂度为 O(eloge),与网中的边数有关,适用于求边稀疏的网的最小生成树。
用int parent[i] 保存每个下标为i的节点的双亲节点的下标。
parent []
初始 parent[]={0},如果parent[i]=0表示下标i的结点没有双亲节点,此时这棵树的根结点就是i。如果parent[i]=n
,n>0,说明i结点的双亲是n。然后在找n的双亲,反复迭代,直到找到这棵树的根结点为止。这也是程序比较巧妙的地方,
只用一个一维数组就表示出了图中所有节点在各个子树中的关系,虽然只能给出每个节点的双亲是哪个,但是对于查找
其所在树的根节点这些信息就足够了。然后通过判断两个节点的所在子树的根节点是否相同,来确定这两个节点是否在
同一个子树上。
由此,可定义一个Find()函数用于查找下标为f节点所在树的根结点。
/* 迭代查找f节点的双亲,直到找到f节点所在树的根结点为止 */
int Find(int *parent, int f)
{
while ( parent[f] > 0)
{
f = parent[f];
}
return f;
}
对边集数组做循环遍历:从权值最小的边开始循环每一条边(完整代码中,在这之前边集数组已经按照边的权值排好序了)
通过判断每条边的begin结点和end结点所在的子树是否有相同的根结点来判断begin和end是否在同一个子树上。如果在同一棵子树上,直接跳过此次循环。
如果没在同一棵子树上,说明begin和end结点可以相连。然后,begin结点所在的树的根结点挂到end节点所在树的根节点上。
for (i = 0; i < G.numEdges; i++)
{
n = Find(parent,edges[i].begin); //查找下标为edges[i].begin结点所在树的根结点
m = Find(parent,edges[i].end); //查找下标为edges[i].end结点所在树的根结点
if (n != m) /* 假如n与m不等,说明两个结点在图中不同的子树上,可以将两个子树相连,使n的双亲是m */
{
parent[n] = m;
//打印edges[i].begin和edges[i].end组成的边
printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
}
}
完整代码如下:
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
#define MAXEDGE 20
#define MAXVEX 20
#define INFINITY 65535
typedef struct
{
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
typedef struct
{
int begin;
int end;
int weight;
}Edge; /* 对边集数组Edge结构的定义 */
/* 构建图 */
void CreateMGraph(MGraph *G)
{
int i, j;
/* printf("请输入边数和顶点数:"); */
G->numEdges=15;
G->numVertexes=9;
for (i = 0; i < G->numVertexes; i++)/* 初始化图 */
{
for ( j = 0; j < G->numVertexes; j++)
{
if (i==j)
G->arc[i][j]=0;
else
G->arc[i][j] = G->arc[j][i] = INFINITY;
}
}
G->arc[0][1]=10;
G->arc[0][5]=11;
G->arc[1][2]=18;
G->arc[1][8]=12;
G->arc[1][6]=16;
G->arc[2][8]=8;
G->arc[2][3]=22;
G->arc[3][8]=21;
G->arc[3][6]=24;
G->arc[3][7]=16;
G->arc[3][4]=20;
G->arc[4][7]=7;
G->arc[4][5]=26;
G->arc[5][6]=17;
G->arc[6][7]=19;
for(i = 0; i < G->numVertexes; i++)
{
for(j = i; j < G->numVertexes; j++)
{
G->arc[j][i] =G->arc[i][j];
}
}
}
/* 交换权值 以及头和尾 */
void Swapn(Edge *edges,int i, int j)
{
int temp;
temp = edges[i].begin;
edges[i].begin = edges[j].begin;
edges[j].begin = temp;
temp = edges[i].end;
edges[i].end = edges[j].end;
edges[j].end = temp;
temp = edges[i].weight;
edges[i].weight = edges[j].weight;
edges[j].weight = temp;
}
/* 对权值进行排序 */
void sort(Edge edges[],MGraph *G)
{
int i, j;
for ( i = 0; i < G->numEdges; i++)
{
for ( j = i + 1; j < G->numEdges; j++)
{
if (edges[i].weight > edges[j].weight)
{
Swapn(edges, i, j);
}
}
}
printf("权排序之后的为:\n");
for (i = 0; i < G->numEdges; i++)
{
printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
}
}
/* 迭代查找f节点的双亲,直到找到f节点所在树的根结点为止 */
int Find(int *parent, int f)
{
while ( parent[f] > 0)
{
f = parent[f];
}
return f;
}
/* 生成最小生成树 */
void MiniSpanTree_Kruskal(MGraph G)
{
int i, j, n, m;
int k = 0;
int parent[MAXVEX]; /* 定义一数组,存放每个节点的双亲 */
Edge edges[MAXEDGE]; /* 定义边集数组,edge的结构为begin,end,weight,均为整型 */
/* 用来构建边集数组并排序********************* */
for ( i = 0; i < G.numVertexes-1; i++)
{
for (j = i + 1; j < G.numVertexes; j++)
{
if (G.arc[i][j]<INFINITY)
{
edges[k].begin = i;
edges[k].end = j;
edges[k].weight = G.arc[i][j];
k++;
}
}
}
sort(edges, &G);
/* ******************************************* */
for (i = 0; i < G.numVertexes; i++)
parent[i] = 0; /* 初始化数组值为0 */
printf("打印最小生成树:\n");
for (i = 0; i < G.numEdges; i++) /* 循环每一条边 */
{
n = Find(parent,edges[i].begin); //查找下标为edges[i].begin结点所在树的根结点
m = Find(parent,edges[i].end); //查找下标为edges[i].end结点所在树的根结点
if (n != m) /* 假如n与m不等,说明两个结点在图中不同的子树上,可以将两个子树相连,使n的双亲是m */
{
parent[n] = m;
//打印edges[i].begin和edges[i].end组成的边
printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
}
}
}
int main(void)
{
MGraph G;
CreateMGraph(&G);
MiniSpanTree_Kruskal(G);
return 0;
}
结果: