对于一个给定的连通的无向图 G = (V, E),希望找到一个无回路的子集 T,T 是 E 的子集,它连接了所有的顶点,且其权值之和为最小。

因为 T 无回路且连接所有的顶点,所以它必然是一棵树,称为生成树(Spanning Tree),因为它生成了图 G。显然,由于树 T 连接了所有的顶点,所以树 T 有 V - 1 条边。一张图 G 可以有很多棵生成树,而把确定权值最小的树 T 的问题称为最小生成树问题(Minimum Spanning Tree)。术语 "最小生成树" 实际上是 "最小权值生成树" 的缩写。

Kruskal 算法提供一种在 O(ElogV) 运行时间确定最小生成树的方案。Kruskal 算法基于贪心算法(Greedy Algorithm)的思想进行设计,其选择的贪心策略就是,每次都选择权重最小的但未形成环路的边加入到生成树中。其算法结构如下:

  1. 将所有的边按照权重非递减排序;
  2. 选择最小权重的边,判断是否其在当前的生成树中形成了一个环路。如果环路没有形成,则将该边加入树中,否则放弃。
  3. 重复步骤 2,直到有 V - 1 条边在生成树中。

上述步骤 2 中使用了 Union-Find 算法来判断是否存在环路。

例如,下面是一个无向连通图 G。

图 G 中包含 9 个顶点和 14 条边,所以期待的最小生成树应包含 (9 - 1) = 8 条边。

首先对所有的边按照权重的非递减顺序排序:

Weight Src Dest
1 7 6
2 8 2
2 6 5
4 0 1
4 2 5
6 8 6
7 2 3
7 7 8
8 0 7
8 1 2
9 3 4
10 5 4
11 1 7
14 3 5

然后从排序后的列表中选择权重最小的边。

1. 选择边 {7, 6},无环路形成,包含在生成树中。

2. 选择边 {8, 2},无环路形成,包含在生成树中。

3. 选择边 {6, 5},无环路形成,包含在生成树中。

4. 选择边 {0, 1},无环路形成,包含在生成树中。

5. 选择边 {2, 5},无环路形成,包含在生成树中。

6. 选择边 {8, 6},有环路形成,放弃。

7. 选择边 {2, 3},无环路形成,包含在生成树中。

8. 选择边 {7, 8},有环路形成,放弃。

9. 选择边 {0, 7},无环路形成,包含在生成树中。

10. 选择边 {1, 2},有环路形成,放弃。

11. 选择边 {3, 4},无环路形成,包含在生成树中。

12. 由于当前生成树中已经包含 V - 1 条边,算法结束。

C# 实现的 Kruskal 算法如下。

 using System;
using System.Collections.Generic;
using System.Linq; namespace GraphAlgorithmTesting
{
class Program
{
static void Main(string[] args)
{
Graph g = new Graph();
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , );
g.AddEdge(, , ); Console.WriteLine();
Console.WriteLine("Graph Vertex Count : {0}", g.VertexCount);
Console.WriteLine("Graph Edge Count : {0}", g.EdgeCount);
Console.WriteLine(); Console.WriteLine("Is there cycle in graph: {0}", g.HasCycle());
Console.WriteLine(); Edge[] mst = g.Kruskal();
Console.WriteLine("MST Edges:");
foreach (var edge in mst)
{
Console.WriteLine("\t{0}", edge);
} Console.ReadKey();
} class Edge
{
public Edge(int begin, int end, int weight)
{
this.Begin = begin;
this.End = end;
this.Weight = weight;
} public int Begin { get; private set; }
public int End { get; private set; }
public int Weight { get; private set; } public override string ToString()
{
return string.Format(
"Begin[{0}], End[{1}], Weight[{2}]",
Begin, End, Weight);
}
} class Subset
{
public int Parent { get; set; }
public int Rank { get; set; }
} class Graph
{
private Dictionary<int, List<Edge>> _adjacentEdges
= new Dictionary<int, List<Edge>>(); public Graph(int vertexCount)
{
this.VertexCount = vertexCount;
} public int VertexCount { get; private set; } public IEnumerable<int> Vertices { get { return _adjacentEdges.Keys; } } public IEnumerable<Edge> Edges
{
get { return _adjacentEdges.Values.SelectMany(e => e); }
} public int EdgeCount { get { return this.Edges.Count(); } } public void AddEdge(int begin, int end, int weight)
{
if (!_adjacentEdges.ContainsKey(begin))
{
var edges = new List<Edge>();
_adjacentEdges.Add(begin, edges);
} _adjacentEdges[begin].Add(new Edge(begin, end, weight));
} private int Find(Subset[] subsets, int i)
{
// find root and make root as parent of i (path compression)
if (subsets[i].Parent != i)
subsets[i].Parent = Find(subsets, subsets[i].Parent); return subsets[i].Parent;
} private void Union(Subset[] subsets, int x, int y)
{
int xroot = Find(subsets, x);
int yroot = Find(subsets, y); // Attach smaller rank tree under root of high rank tree
// (Union by Rank)
if (subsets[xroot].Rank < subsets[yroot].Rank)
subsets[xroot].Parent = yroot;
else if (subsets[xroot].Rank > subsets[yroot].Rank)
subsets[yroot].Parent = xroot; // If ranks are same, then make one as root and increment
// its rank by one
else
{
subsets[yroot].Parent = xroot;
subsets[xroot].Rank++;
}
} public bool HasCycle()
{
Subset[] subsets = new Subset[VertexCount];
for (int i = ; i < subsets.Length; i++)
{
subsets[i] = new Subset();
subsets[i].Parent = i;
subsets[i].Rank = ;
} // Iterate through all edges of graph, find subset of both
// vertices of every edge, if both subsets are same,
// then there is cycle in graph.
foreach (var edge in this.Edges)
{
int x = Find(subsets, edge.Begin);
int y = Find(subsets, edge.End); if (x == y)
{
return true;
} Union(subsets, x, y);
} return false;
} public Edge[] Kruskal()
{
// This will store the resultant MST
Edge[] mst = new Edge[VertexCount - ]; // Step 1: Sort all the edges in non-decreasing order of their weight
// If we are not allowed to change the given graph, we can create a copy of
// array of edges
var sortedEdges = this.Edges.OrderBy(t => t.Weight);
var enumerator = sortedEdges.GetEnumerator(); // Allocate memory for creating V ssubsets
// Create V subsets with single elements
Subset[] subsets = new Subset[VertexCount];
for (int i = ; i < subsets.Length; i++)
{
subsets[i] = new Subset();
subsets[i].Parent = i;
subsets[i].Rank = ;
} // Number of edges to be taken is equal to V-1
int e = ;
while (e < VertexCount - )
{
// Step 2: Pick the smallest edge. And increment the index
// for next iteration
Edge nextEdge;
if (enumerator.MoveNext())
{
nextEdge = enumerator.Current; int x = Find(subsets, nextEdge.Begin);
int y = Find(subsets, nextEdge.End); // If including this edge does't cause cycle, include it
// in result and increment the index of result for next edge
if (x != y)
{
mst[e++] = nextEdge;
Union(subsets, x, y);
}
else
{
// Else discard the nextEdge
}
}
} return mst;
}
}
}
}

输出结果如下:

参考资料

本篇文章《Kruskal 最小生成树算法》由 Dennis Gao 发表自博客园,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载行为均为耍流氓。

Kruskal 最小生成树算法的更多相关文章

  1. [算法系列之二十七]Kruskal最小生成树算法

    简单介绍 求最小生成树一共同拥有两种算法,一个是就是本文所说的Kruskal算法,还有一个就是Prime算法. 在具体解说Kruskal最小生成树算法之前,让我们先回想一下什么是最小生成树. 我们有一 ...

  2. 并查集和kruskal最小生成树算法

    并查集 先定义 int f[10100];//定义祖先 之后初始化 for(int i=1;i<=n;++i) f[i]=i; //初始化 下面为并查集操作 int find(int x)//i ...

  3. [算法] kruskal最小生成树算法

    #include <stdio.h> #include <stdlib.h> #define MAX 100 int N, M; struct Edge { int u,v; ...

  4. 贪心算法(2)-Kruskal最小生成树

    什么是最小生成树? 生成树是相对图来说的,一个图的生成树是一个树并把图的所有顶点连接在一起.一个图可以有许多不同的生成树.一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n ...

  5. 最小生成树算法(Prim,Kruskal)

    边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用:要连通n个城市需要n-1条边线路.可以 ...

  6. c/c++ 用克鲁斯卡尔(kruskal)算法构造最小生成树

    c/c++ 用克鲁斯卡尔(kruskal)算法构造最小生成树 最小生成树(Minimum Cost Spanning Tree)的概念: 假设要在n个城市之间建立公路,则连通n个城市只需要n-1条线路 ...

  7. [数据结构]最小生成树算法Prim和Kruskal算法

    最小生成树 在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树.  例如,对于如上图G4所示的连通网可以有多棵权值总 ...

  8. 无向带权图的最小生成树算法——Prim及Kruskal算法思路

    边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用:要连通n个城市需要n-1条边线路.可以 ...

  9. 最小生成树之克鲁斯卡尔(Kruskal)算法

    学习最小生成树算法之前我们先来了解下 下面这些概念: 树(Tree):如果一个无向连通图中不存在回路,则这种图称为树. 生成树 (Spanning Tree):无向连通图G的一个子图如果是一颗包含G的 ...

随机推荐

  1. Eclipse下配置javaweb项目快速部署到tomcat

    用惯了VS,再用Eclipse,完全有一种从自动挡到手动挡的感觉啊. 很多同学在Eclipse下开发web项目,每一次修改代码,看效果的时候都有右键项目->Run as -> Run on ...

  2. 【leetcode】Longest Common Prefix

    题目简述: Write a function to find the longest common prefix string amongst an array of strings. 解题思路: c ...

  3. 迭代器模式/iterator模式/对象行为型模式

    意图 又名:游标(Cursor): 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示. 动机 一个聚合对象,提供访问元素的方法,而有不暴露它的内部结构.如list,将对列表的访问 ...

  4. for循环每次取出一个字符(不是字节)

    python3.5 for循环每次取出一个字符(不是字节) #!/usr/bin/env python # -*- coding:utf-8 -*- my_str = "我是哈哈" ...

  5. 【maven】pom.xml报错:Cannot detect Web Project version.

    新建的maven项目 报错如下: Cannot detect Web Project version. Please specify version of Web Project through &l ...

  6. Nginx - 配置

    1 自动显示目录 autoindex on; 1.1 显示文件大小 autoindex_exact_size off; 默认为on,显示出文件的确切大小,单位是bytes. 改为off后,显示出文件的 ...

  7. tecplot 脚本文件

    一个简单的C++代码,可以生成tecplot的脚本文件,打开文件就能自动绘图.绘图过程是先把文件导入,然后镜像,最后生成一个动画,但是导入的文件名称要求是有规律的. /* ** This progra ...

  8. vsftp匿名用户搭建

    ./configure出现: 请装: 出现这个: 请装 然后: ln -sv /lib/security/pam_mysql.so /lib64/security/

  9. React生命周期

    在react生命周期中,分2段执行,一个挂载的生命周期,一个是组件发生了数据变动,或者事件触发而引发的更新生命周期. 注:react生命周期很重要,对于很多组件场景的应用发挥重要作用,而且不熟悉生命周 ...

  10. C程序语法(无左递归)

    <程序> -〉 <外部声明> | <函数定义><外部声明> -〉<头文件> | <变量> | <结构体> <头 ...