什么是最小生成树?
生成树是相对图来说的,一个图的生成树是一个树并把图的所有顶点连接在一起。一个图可以有许多不同的生成树。一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树其实是最小权重生成树的简称。生成树的权重是考虑到了生成树的每条边的权重的总和。
最小生成树有几条边?
最小生成树有(V – 1)条边,其中V是给定的图的顶点数量。
Kruskal算法
下面是步骤寻找MST使用Kruskal算法
3 |
2,选择最小的边。检查它是否形成与当前生成树形成环。如果没有形成环,讲这条边加入生成树。否则,丢弃它。 |
步骤2使用并查集算法来检测环。如果不熟悉并查集建议阅读下并查集。
该算法是一种贪心算法。贪心的选择是选择最小的权重的边,并不会和当前的生成树形成环。让我们了解一个例子,考虑下面输入图
spanning-tree-mst
该图包含9个顶点和14个边。因此,形成最小生成树将有(9 – 1)= 8条边。
现在从已经排序的边中逐个选择
1. edge 7-6:没有环,加入
2. edge 8-2: 没有环,加入
3. edge 6-5: 没有环,加入
4. edge 0-1: 没有环,加入
5. edge 2-5: 没有环,加入
6. edge 8-6: 加入后会形成环,舍弃
7. edge 2-3: 没有环,加入
8. edge 7-8: 加入后会形成环,舍弃
9. edge 0-7: 没有环,加入
10. edge 1-2: 加入后会形成环,舍弃
11. edge 3-4: 没有环,加入
目前为止一家有了 V-1 条边,可以肯定V个顶点都一包含在内,到此结束。
代码实现:
// Kruskal 最小生成树算法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 带有权重的边
struct Edge
{
int src, dest, weight;
};
// 无向图
struct Graph
{
// V-> 顶点个数, E->边的个数
int V, E;
// 由于是无向图,从 src 到 dest的边,同时也是 dest到src的边,按一条边计算
struct Edge* edge;
};
//构建一个V个顶点 E条边的图
struct Graph* createGraph(int V, int E)
{
struct Graph* graph = (struct Graph*) malloc( sizeof(struct Graph) );
graph->V = V;
graph->E = E;
graph->edge = (struct Edge*) malloc( graph->E * sizeof( struct Edge ) );
return graph;
}
//并查集的结构体
struct subset
{
int parent;
int rank;
};
// 使用路径压缩查找元素i
int find(struct subset subsets[], int i)
{
if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}
// 按秩合并 x,y
void Union(struct subset subsets[], int x, int y)
{
int xroot = find(subsets, x);
int yroot = find(subsets, y);
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
else
{
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}
// 很据权重比较两条边
int myComp(const void* a, const void* b)
{
struct Edge* a1 = (struct Edge*)a;
struct Edge* b1 = (struct Edge*)b;
return a1->weight > b1->weight;
}
// Kruskal 算法
void KruskalMST(struct Graph* graph)
{
int V = graph->V;
struct Edge result[V]; //存储结果
int e = 0; //result[] 的index
int i = 0; // 已排序的边的 index
//第一步排序
qsort(graph->edge, graph->E, sizeof(graph->edge[0]), myComp);
// 为并查集分配内存
struct subset *subsets =
(struct subset*) malloc( V * sizeof(struct subset) );
// 初始化并查集
for (int v = 0; v < V; ++v)
{
subsets[v].parent = v;
subsets[v].rank = 0;
}
// 边的数量到V-1结束
while (e < V - 1)
{
// Step 2: 先选最小权重的边
struct Edge next_edge = graph->edge[i++];
int x = find(subsets, next_edge.src);
int y = find(subsets, next_edge.dest);
// 如果此边不会引起环
if (x != y)
{
result[e++] = next_edge;
Union(subsets, x, y);
}
// 否则丢弃,继续
}
// 打印result[]
printf("Following are the edges in the constructed MST\n");
for (i = 0; i < e; ++i)
printf("%d -- %d == %d\n", result[i].src, result[i].dest,
result[i].weight);
return;
}
// 测试
int main()
{
/* 创建下面的图:
10
0--------1
| \ |
6| 5\ |15
| \ |
2--------3
4 */
int V = 4; // 顶点个数
int E = 5; //边的个数
struct Graph* graph = createGraph(V, E);
// 添加边 0-1
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[0].weight = 10;
graph->edge[1].src = 0;
graph->edge[1].dest = 2;
graph->edge[1].weight = 6;
graph->edge[2].src = 0;
graph->edge[2].dest = 3;
graph->edge[2].weight = 5;
graph->edge[3].src = 1;
graph->edge[3].dest = 3;
graph->edge[3].weight = 15;
graph->edge[4].src = 2;
graph->edge[4].dest = 3;
graph->edge[4].weight = 4;
KruskalMST(graph);
return 0;
}
运行结果如下:
Following are the edges in the constructed MST
2 -- 3 == 4
0 -- 3 == 5
0 -- 1 == 10
时间复杂度:
O(ElogE) 或 O(ElogV)。 排序使用 O(ELogE) 的时间,之后我们遍历中使用并查集O(LogV) ,所以总共复杂度是 O(ELogE + ELogV)。E的值最多为V^2,所以
O(LogV) 和 O(LogE) 可以看做是一样的。