最小生成树,是一个十分重要的知识点。最早学它的时候,觉得它的算法思想很朴素,实现起来也很简单,但是伴随着后来的学习,我发现,最小生成树算法确实十分常用,算法题中也常常会手撸一个最小生成树,在这里,在把最小生成树相关知识点整理一下。
定义
最小生成树,首先,它是一棵基于图生成的树。它符合树的定义,并且包含了原图中的所有节点。同时我们要求,这棵树,其边相连的权重和是所有生成树中最小的,这就是最小生成树。
应用
最小生成树的应用十分广泛,我在各种与图相关的应用场景都见过,比如计算机网络,决策树,金融数据分析等。
贪婪算法
我们首先看一下最小生成树算法的一个基本思路。
原文中讲的有些复杂,其实原理韩式比较简单的。
首先,我们认为所有的初始边都是灰色的,之后我们依次选取灰色边中最小的变成黑边,这个黑边还要满足连接的两个点不能是之前已经被黑边连接过的。假设一共有n个点,我们执行上述过程n-1次,就得到了一棵最小生成树。
后续实现中用到的API
public class Edge implements Comparable<Edge>
{
private final int v, w;
private final double weight;
public Edge(int v, int w, double weight)
{
this.v = v;
this.w = w;
this.weight = weight;
}
public int either()
{ return v; }
public int other(int vertex)
{
if (vertex == v) return w;
else return v;
}
public int compareTo(Edge that)
{
if (this.weight < that.weight) return -1;
else if (this.weight > that.weight) return +1;
else return 0;
}
}
public class EdgeWeightedGraph
{
private final int V;
private final Bag<Edge>[] adj;
public EdgeWeightedGraph(int V)
{
this.V = V;
adj = (Bag<Edge>[]) new Bag[V];
for (int v = 0; v < V; v++)
adj[v] = new Bag<Edge>();
}
public void addEdge(Edge e)
{
int v = e.either(), w = e.other(v);
adj[v].add(e);
adj[w].add(e);
}
public Iterable<Edge> adj(int v)
{ return adj[v]; }
}
Kruskal's algorithm
算法超简单啊,和上面提到的贪婪算法如出一辙。每次挑选权重最小的边,只要不形成环,就把这条边加进入,直到形成树为止。实现的时候最大的困难就在于如何判断会不会形成环,这个问题在算法的第一讲就已经讲到过了,我们可以使用并查集,来完成这个操作。
public class KruskalMST
{
private Queue<Edge> mst = new Queue<Edge>();
public KruskalMST(EdgeWeightedGraph G)
{
MinPQ<Edge> pq = new MinPQ<Edge>();
for (Edge e : G.edges())
pq.insert(e);
UF uf = new UF(G.V());
while (!pq.isEmpty() && mst.size() < G.V()-1)
{
Edge e = pq.delMin();
int v = e.either(), w = e.other(v);
if (!uf.connected(v, w))
{
uf.union(v, w);
mst.enqueue(e);
}
}
}
public Iterable<Edge> edges()
{ return mst; }
}
具体的题目应用可以看我的另一篇博客数据中心
算法复杂度为ElogE