关于最小生成树的一切
Posted C_YCBX Py_YYDS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于最小生成树的一切相关的知识,希望对你有一定的参考价值。
文章目录
初识最小生成树
首先,小伙伴们可能要冒出第一个问题了。什么是生成树?生成树 指的是「无向图」中,具有该图的 全部顶点 且 边数最少 的连通子图。「图8. 生成树」中,所有粉色线条组成的一棵树[(A, B), (A, C), (A, D), (A, E)],就是该无向图的其中一个生成树。其实[(A, E),(A, B), (B, C), (C, D)]也是该无向图的一个生成树。由此可见,一个「无向图」的生成树可以是多个。
那么再了解了什么是生成树后,小伙伴们可能又要冒出第二个问题了。什么是最小生成树。最小生成树指的是「加权无向图」中总权重最小的生成树。「图9. 最小生成树」中,所有绿色线条组成的一颗生成树[(A, E),(A, B), (B, C), (C, D)],就是该加权无向图的其中一个最小生成树。其实[(A, E), (E, D), (A, B), (B, C)]也是该加权无向图的另一个最小生成树,由此可见,一个「加权无向图」的最小生成树可以是多个。
那么在该章节中,我们将学习下「生成最小生成树」的两种算法以及「切分定理」:
- 切分定理
- Kruskal 算法
- Prim 算法
切分定理
「切分」是什么呢?很多的定理都是以人的名字命名的,但是「切分」并不是一个人的名字。在「切分定理」中有两个基本概念,我们需要了解下:
- 切分:将「图」切成两个部分,称之为一个「切分」。「图 10. 切分图」就是一个「切分」,其中(B, A, E)为一个部分,(C, D)为另外一个部分。
- 横切边:如果一条边连接的两个顶点属于切分的两个部分,这个边称为「横切边」。在「图10. 切分图」中,(B, C), (A, C), (A, D), (E, D) 均为「横切边」。
再了解了切分定理的基础概念之后,我们就需要学习下「切分定理」了。切分定理是 Kruskal 算法和 Prim 算法的重要的理论支撑。那么什么是「切分定理」呢?根据 维基百科 的定义,「切分定理」指的是:
在一幅连通加权无向图中,给定任意的切分,如果有一条横切边的权值严格小于所有其他横切边,则这条边必然属于图的最小生成树中的一条边。
切分定理的证明
Kruskal 算法(以边扩散)
「Kruskal 算法」是求解「加权无向图」的「最小生成树」的一种算法。
视频链接
时间复杂度:
O
(
E
∗
l
o
g
E
)
O(E*logE)
O(E∗logE)
E
E
E 表示边数。
空间复杂度:
O
(
V
)
O(V)
O(V)
V
V
V表示顶点数。
练习题–连接所有点的最小费用
解题代码
class Solution {
// Kruskal Algorithm
public int minCostConnectPoints(int[][] points) {
if (points == null || points.length == 0) {
return 0;
}
int size = points.length;
PriorityQueue<Edge> pq = new PriorityQueue<Edge>((x, y) -> x.cost - y.cost);
UnionFind uf = new UnionFind(size);
for (int i = 0; i < size; i++) {
for (int j = i+1; j < size; j++) {
int[] coordinate1 = points[i];
int[] coordinate2 = points[j];
// Calculate the distance between two coordinates.
int cost = Math.abs(coordinate1[0] - coordinate2[0]) + Math.abs(coordinate1[1] - coordinate2[1]);
Edge edge = new Edge(i, j, cost);
pq.add(edge);
}
}
int result = 0;
int count = size - 1;
while ( pq.size() > 0 && count > 0 ) {
Edge e = pq.poll();
if ( !uf.connected(e.point1, e.point2)) {
uf.union(e.point1, e.point2);
result += e.cost;
count--;
}
}
return result;
}
class Edge {
int point1;
int point2;
int cost;
Edge(int point1, int point2, int cost) {
this.point1 = point1;
this.point2 = point2;
this.cost = cost;
}
}
class UnionFind {
int root[];
int rank[];
public UnionFind(int size) {
root = new int[size];
rank = new int[size];
for (int i = 0; i < size; i++) {
root[i] = i;
rank[i] = 1;
}
}
public int find(int x) {
if (x == root[x]) {
return x;
}
return root[x] = find(root[x]);
}
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] > rank[rootY]) {
root[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
root[rootX] = rootY;
} else {
root[rootY] = rootX;
rank[rootX] += 1;
}
}
}
public boolean connected(int x, int y) {
return find(x) == find(y);
}
}
}
Prim算法(以顶点扩散)
「Prim 算法」是求解「加权无向图」的「最小生成树」的另一种算法。
视频链接
算法证明
视频链接
「Kruskal 算法」和 「Prim 算法」区别
在「Kruskal 算法」中,我们通过增加边数来扩大「最小生成树」;
在「Prim 算法」中,我们通过增加顶点来扩大「最小生成树」。
时间复杂度
普通二叉堆: O ( E l o g V ) O ( E l o g V ) O(ElogV)O(ElogV) O(ElogV)O(ElogV)。
斐波那契堆: O ( E + V l o g V ) O ( E + V l o g V ) O(E+VlogV)O(E+VlogV) O(E+VlogV)O(E+VlogV)。
V V V 表示顶点数, E E E 表示边数。
空间复杂度
O ( V ) O(V) O(V)。
V V V表示顶点数。
练习题–连接所有点的最小费用
题目上面有图
解题代码
class Solution {
// Prim Algorithm
public int minCostConnectPoints(int[][] points) {
if (points == null || points.length == 0) {
return 0;
}
int size = points.length;
PriorityQueue<Edge> pq = new PriorityQueue<Edge>((x, y) -> x.cost - y.cost);
boolean[] visited = new boolean[size];
int result = 0;
int count = size - 1;
// Add all edges from points[0] vertexs
for (int j = 1; j < size; j++) {
// Calculate the distance between two coordinates.
int[] coordinate1 = points[0];
int[] coordinate2 = points[j];
int cost = Math.abs(coordinate1[0] - coordinate2[0]) + Math.abs(coordinate1[1] - coordinate2[1]);
Edge edge = new Edge(0, j, cost);
pq.add(edge);
}
visited[0] = true;
while (pq.size() > 0 && count > 0) {
Edge e = pq.poll();
int point1 = e.point1;
int point2 = e.point2;
int cost = e.cost;
if ( !visited[point2] ) {
result += cost;
visited[point2] = true;
for (int j = 0; j < size; j++ ) {
if ( !visited[j] ) {
int distance = Math.abs(points[point2][0] - points[j][0]) + Math.abs(points[point2][1] - points[j][1]);
pq.add(new Edge(point2, j, distance));
}
}
count--;
}
}
return result;
}
class Edge {
int point1;
int point2;
int cost;
Edge(int point1, int point2, int cost) {
this.point1 = point1;
this.point2 = point2;
this.cost = cost;
}
}
}
以上是关于关于最小生成树的一切的主要内容,如果未能解决你的问题,请参考以下文章