MST性质
假设N={V, {E}}是一个连通网,U是顶点集V的一个非空子集,若(u, v)是一条具有最小权值的边,其中u∈U, v∈V-U,则比存在一棵包含边(u, v)的最小生成树
最小生成树的Prim算法描述
假设集合U里面存放的是已经在最小生成树中的顶点,集合TE存放的是最小生成树的边,算法从U={u0}开始,在所有u∈U,v∈V-U的所有边(u, v)中选一条权值最小的边加入生成树,同时v0加入顶点集合U中,重复上述操作,直至所有顶点都已经加入顶点集合为止
一个鲜活的例子
代码实现
//prim算法的最小生成树//
void minispanTree_prim(MGraph *G) {
//adjvex表示最小权值边的起始顶点,j表示结束顶点
int min, i, j, k;//会用到的变量
int adjvex[MAXVEX];//用来存放边的其中一个顶点
int lowcost[MAXVEX];//用来存放当前已经连通部分的最小权值(对应于adjvex中的顶点)
lowcost[0] = 0;//初始化第一个数值为0,表示第一个顶点已经加入最小生成树
adjvex[0] = 0;//第一个顶点下标
for (i = 1; i < G->Vertex_num; i++) {
lowcost[i] = G->arc[0][i];
adjvex[i] = 0;//一开始最小生成树的顶点只有0
}
//初始化完成,接下来开始生成最小生成树
for (i = 1; i < G->Vertex_num; i++) {
//这里表示有n个顶点要弄出n-1条边(包括v0有n个顶点)
min = INFINITY;//初始化最小值为无穷
j = 1;
k = 0;//k用于存放当前权值最小边的其中一个下标
while (j < G->Vertex_num) {
//循环出了第一个外的全部顶点
if (lowcost[j] != 0 && lowcost[j] < min) {
//寻找没有完成(不为0),并且是最小的顶点的下标
//也就是在可接触的最小权值边(lowcost)中选一个最小的
min = lowcost[j];
k = j;
}
j++;
}
//此时,k存放的就是当前可以接触到的最小权值边的一个顶点
//adjvex[k]记录的是当前所能接触到的最小权值边的另一个顶点
printf("(%d,%d)\n", adjvex[k], k);
lowcost[k] = 0;//表示这个顶点已经加入到最小生成树中
//接下来要寻找新的可以接触到的最小权值边
for (j = 1; j < G->Vertex_num; j++) {
if (lowcost[j] != 0 && G->arc[k][j] < lowcost[j]) {
//k表示新的最小生成树顶点,可以通过它来寻找最小权值边
lowcost[j] = G->arc[k][j];
adjvex[j] = k;//找到了之后要把其中一个顶点改为k,表示到k的最小权值边
}
}
}
}
Kruskal算法描述
首先将所有顶点都加入最小生成树的顶点集S中,然后每次选择权值最小的边,选择完之后要判断是否形成回路,如果形成回路的话就要重新选一条权值次之的边,如此循环,知道选择完n-1条边就构成了一棵最小生成树
一个鲜活的例子
代码实现(比较复杂)
//Kruskal算法的最小生成树//
//定义边集数组
typedef struct {
int begin;//开始顶点的下标
int end; //结束顶点的下标
int weight;//边的权重
}Edge;
//生成边集数组
void Create_array(MGraph *G,Edge edges[]) {
int i,j,count=0;
for (i = 0; i < G->Vertex_num; i++) {
for (j = i + 1; j < G->Vertex_num; j++) {
//因为是无向图,所以只要遍历矩形的一半(对角线也排除了)
if (G->arc[i][j] != 0 && G->arc[i][j] != INFINITY) {
//如果边存在的话
edges[count].begin = i;//开始顶点的下标
edges[count].end = j;//结束顶点的下标
edges[count].weight = G->arc[i][j];//该边的权重
count++;
}
}
}
//接下来给边集数组排序(只会冒泡法了)
//出循环时边集数组的数量是count-1
//排序要求从小到大
for (i = 0; i < count; i++) {
for (j = i + 1; j < count; j++) {
if (edges[i].weight > edges[j].weight) {
//如果前一个小于后一个的话就交换位置
Edge temp;
temp = edges[i];
edges[i] = edges[j];
edges[j] = temp;
}
}
}
//打印边集数组
printf("边集数组为:\n");
printf("\tbegin\tend\tweight\n");
for (i = 0; i < count; i++) {
printf("edges[%d] %d\t%d\t%d\n", i, edges[i].begin, edges[i].end, edges[i].weight);
}
}
//查询函数,用于查找如果加入这条边的话是否存在存在回路
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[MAXEDGE];//用于存放边集数组
int parent[MAXVEX]; //用于存放最小生成树的边,0表示还没有加入最小生成树
//数组下标表示边的起始点下标,数组内的值表示边的结束点下标
//先生成边集数组
Create_array(G, edges);
//初始化parent数组
for (i = 0; i < G->Vertex_num; i++) {
parent[i] = 0;
}
//接着循环遍历每一条边
printf("生成最小生成树(Kruskal算法):\n");
for (i = 0; i < G->Edge_num; i++) {
n = Find(parent, edges[i].begin);
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].weight);
}
}
}