Kruskal 算法(最小生成树)

本文深入探讨了最小生成树的概念,详细介绍了Kruskal算法的原理与实现方法,并通过时间复杂度分析展示了其效率。同时,文章还强调了最小生成树在解决实际问题中的重要性。

最小生成树 Minimal Spanning Tree(MST)问题
已知一个连通图G={V,E}(G: graph, V: vector, E: edge),求子图G’={V,E’},使得子图里的边权总和最小。

也就是说,G’ 仍然是一个连通图,但是边的数量要删减。

为什么是最小生成

假设 G’存在环,那么删去这个环里的任意一条边都不影响整个图的连通性,但边权总和会更小。对了,要补充一点:这个连通图没有负环(但不排除存在负边权)。否则,删去负环中的任何一条边反而会使边权总和变大。

原理

Kruskal算法本质上就是一个贪心算法。
1、将G所有条边按权从小到大排序,MST开始为空。
2、从小到大次序取边(u,v)。
3、若加入边(u,v),MST就有环,则放弃此边,转2。
4、将边(u,v)加入MST,如果已经加了E-1条边,结束。否则转2。

实现方法

最容易想到的方法是暴力,但用并查集的实现则更简洁高效。
在算法原理中,最难实现的就是MST是否有环。这个问题事实上可以转化为 u 和 v 是否在同一连通块里面,这样用并查集则巧妙地解决了该问题,只要判断两个顶点的“祖先”是否相同即可。
另外一点,要证明:当 u 与 v 在不同的连通块时,加入边(u,v)一定是最优的。这一点很简单:在之后的边里面,即使能连通 u 与 v,权值也明显会大于(u,v)的权值。

//Kruskal 算法
for (int i=0; i<V; i++) f[i]=i;         //并查集初始化
sort(edge, edge+E, cmp);                //将边按权重排序
int MST_edge=0, MST_sum_weight;         //MST的边的数量、MST的权重总和 
for (int i=0; i<E; i++)
{
    u = find_set(edge[i].u);            //找这两个节点的“祖先” 
    v = find_set(edge[i].v);
    if (u != v)
    {
        MST_sum_weight += edge[i].weight; 
        union_set(u, v);                //合并两个连通块 
        MST_edge++;
        if (MST_edge == E-1) break;     //可省,对时间复杂度影响不大 
    }
}

时间复杂度

时间复杂度大约为O(|E|log2|E|+α|V|),α是阿克曼函数的一个增长速度非常缓慢的反函数,在实际运算中不会超过5,因此当作常数也可,这里不再深究。

### Kruskal算法最小生成树C语言实现 Kruskal算法是一种用于寻找加权图中的最小生成树的有效方法。该算法通过逐步添加权重最低且不会形成环路的边来构建最小生成树。 #### 数据结构定义 为了有效地执行Kruskal算法,通常会使用并查集(Union-Find)数据结构来检测和防止循环的发生。以下是必要的数据结构定义: ```c #include <stdio.h> #include <stdlib.h> // 定义一个联合体表示边 typedef struct { int src; int dest; int weight; } Edge; // 图的数据结构 typedef struct Graph { int V; // 节点数量 int E; // 边的数量 Edge* edge; // 所有边数组 } Graph; Graph* createGraph(int V, int E); int find(int parent[], int i); void Union(int parent[], int rank[], int x, int y); int compare(const void *a, const void *b); /// 创建一个新的图实例 Graph* createGraph(int V, int E) { Graph* graph = (Graph*) malloc(sizeof(Graph)); graph->V = V; graph->E = E; graph->edge = (Edge*) malloc(graph->E * sizeof(Edge)); return graph; } ``` #### 并查集操作函数 这些辅助函数实现了路径压缩启发式的查找以及按秩合并的操作,有助于提高效率。 ```c // 查找子节点所属集合代表元素,并做路径压缩优化 int find(int parent[], int i) { if (parent[i] == -1) return i; return parent[i] = find(parent, parent[i]); } // 合并两个不相交集合 void Union(int parent[], int rank[], int x, int y) { int xroot = find(parent, x); int yroot = find(parent, y); // 始终让较小rank成为根结点 if (rank[xroot] < rank[yroot]) parent[xroot] = yroot; else if (rank[xroot] > rank[yroot]) parent[yroot] = xroot; else { // 如果两者相同,则任意选其一作为新的父节点并将它的等级增加一层 parent[yroot] = xroot; rank[xroot]++; } } ``` #### 排序比较器 此部分提供了qsort所需的回调函数,以便按照每条边的成本升序排列所有的边。 ```c // qsort()使用的比较器:依据边成本从小到大排序 int compare(const void *a, const void *b) { Edge *e1 = (Edge *) a; Edge *e2 = (Edge *) b; return e1->weight > e2->weight ? 1 : -1; } ``` #### 主要逻辑流程控制 最后,在主程序中调用上述组件完成整个过程。 ```c void KruskalMST(Graph *graph) { int V = graph->V; Edge result[V]; // 存储最终的结果即构成MST的所有边 int e = 0; // 结果集中当前已处理过的边数计数器 int i = 0; // 迭代遍历原始输入边列表时所用索引变量 // 对所有边根据它们各自的cost属性值进行非降序重排 qsort(graph->edge, graph->E, sizeof(graph->edge[0]), compare); // 初始化各顶点所在连通分量信息表及其对应的高度记录向量 int *parent = (int *)malloc(V * sizeof(int)); int *rank = (int *)malloc(V * sizeof(int)); for (i = 0; i < V; ++i){ parent[i] = -1; rank[i] = 0; } // 遍历经过排序后的候选边序列尝试加入结果集直至达到所需规模为止 for(i=0;i<graph->E && e<V-1;++i){ Edge next_edge = graph->edge[i]; int x = find(parent, next_edge.src); int y = find(parent, next_edge.dest); if(x != y){ result[e++] = next_edge; Union(parent, rank, x, y); } } 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 ); } ``` 以上就是基于C语言实现Kruskal算法的具体细节[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值