求的带权图最小生成树的Prim算法和Kruskal算法

求的带权图最小生成树的Prim算法和Kruskal算法

最小生成树的概念

最小生成树其实是最小权重生成树的简称。

一个连通图可能有多个生成树。当图中的边具有权值时,总会有一个生成树的边的权值之和小于或者等于其它生成树的边的权值之和。广义上而言,对于非连通无向图来说,它的每一连通分量同样有最小生成树,它们的并被称为最小生成森林。

以有线电视电缆的架设为例,若只能沿着街道布线,则以街道为边,而路口为顶点,其中必然有一最小生成树能使布线成本最低。

相关性质

存在个数

这张图表明一个图中可能有多个最小生成树
最小生成树在一些情况下可能会有多个。例如,当图的每一条边的权值都相同时,该图的所有生成树都是最小生成树。

如果图的每一条边的权值都互不相同,那么最小生成树将只有一个。这一定理同样适用于最小生成森林。

边的权值之和最低的子图

如果图的边的权值都为正数,那么最小生成树就是该图的所有包含所有顶点的子图中权值最低的子图。

环定理

对于连通图中的任意一个环:如果C中有边 e的权值大于该环中任意一个其它的边的权值,那么这个边不会是最小生成树中的边

切分定理

在一幅连通加权无向图中,给定任意的切分,它的横切边中权值最小的边必然属于图的最小生成树。

最小权值边

如果图的具有最小权值的边只有一条,那么这条边包含在任意一个最小生成树中。

相关算法

Prim算法

Prim算法的每一步都会为一棵生长中的树添加一条边,该树最开始只有一个顶点,然后会添加 V-1个边。每次总是添加生长中的树和树中除该生长的树以外的部分形成的切分的具有最小权值的横切边。Prim算法使用的就是切分定理思想
使用堆的LazyPrim算法的时间复杂度为$O(ElogE)$
使用索引堆优化过的Prim算法的时间复杂度为$O(ElogV)$

Kruskal算法

按照边的权重顺序(从小到大)将边加入生成树中,但是若加入该边会与生成树形成环则不加入该边。直到树中含有 V-1条边为止。这些边组成的就是该图的最小生成树。
Kruskal算法使用的是环定理思想
Kruskal算法的时间复杂度为$ElogE$

代码实现

LazyPrim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package MinimumSpanTrees;

import java.util.Vector;

/**
* @ Description: LazyPrim算法求最小生成树
* @ Date: Created in 08:38 2018/8/2
* @ Author: Anthony_Duan
*/
public class LazyPrimMST<Weight extends Number & Comparable> {

private WeightedGraph<Weight> G;
private MinHeap<Edge<Weight>> pq;
private boolean[] marked;
private Vector<Edge<Weight>> mst;
private Number mstWeight;

public LazyPrimMST(WeightedGraph<Weight> graph) {

//初始化算法
G = graph;
//开辟一个空间为图的边数的最小堆
pq = new MinHeap<Edge<Weight>>(G.E());
//开辟一个数组用来标记是否已经访问过
marked = new boolean[G.V()];
//保存最小生成树的边
mst = new Vector<Edge<Weight>>();


//首先从0索引开始访问
visit(0);
//如果堆不为空 也就是堆中仍有边
while (!pq.isEmpty()) {
//使用最小堆找出已经访问的边中权值最小的边
Edge<Weight> e = pq.extractMin();

//如果这个边的两个端点都已经访问过了,则这个边不符合条件
if (marked[e.v()] == marked[e.w()]) {
continue;
}
//如果有一个端点没有访问就把该边添加到mst中
mst.add(e);
//继续访问这条边没有被访问的那个节点
if (!marked[e.v()]) {
visit(e.v());
} else {
visit(e.w());
}
}

/**
* 计算最小生成树的权值
* 这里没有 这么写
* mstWeight = 0;
* for (int i = 0; i < mst.size(); i++) {
* mstWeight =mstWeight.doubleValue()+mst.elementAt(i).wt().doubleValue();
* }
*
* 是因为我们的mstWeight这个变量不一定是整型(或者浮点型)。
* 而是Weight类型。Weight有可能是用户自定义的类型。
* 比如Weight是用户自己定义的大整数类型;高精度浮点数类性;
* 或者复数类型;向量;等等。此时,直接赋值为0是不对的,
* 需要调用用户自定义的无参数构造函数,得到这个自定义类型的“零”。
*/

mstWeight = mst.elementAt(0).wt();
for (int i = 1; i <mst.size() ; i++) {
mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
}

}

/**
*
* @param v
*/
private void visit(int v) {
assert !marked[v];
//将节点标记为访问过
marked[v] = true;

//将和节点v相连接的所有未访问的边放入最小堆中
for (Edge<Weight> e :
G.adj(v)) {
if (!marked[e.other(v)]) {
pq.insert(e);
}
}
}

Vector<Edge<Weight>> mstEdges() {
return mst;
}

Number result() {
return mstWeight;
}


// 测试 Lazy Prim
public static void main(String[] args) {

String filename = "/Users/duanjiaxing/IdeaProjects/Algorithm/src/MinimumSpanTrees/testG1.txt";
int V = 8;

SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

// Test Lazy Prim MST
System.out.println("Test Lazy Prim MST:");
LazyPrimMST<Double> lazyPrimMST = new LazyPrimMST<Double>(g);
Vector<Edge<Double>> mst = lazyPrimMST.mstEdges();
for( int i = 0 ; i < mst.size() ; i ++ ) {
System.out.println(mst.elementAt(i));
}
System.out.println("The MST weight is: " + lazyPrimMST.result());

System.out.println();
}
}
Prim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package MinimumSpanTrees;


import java.util.Vector;

/**
* @ Description:使用优化的Prim算法求图的最小生成树
* @ Date: Created in 09:46 2018/8/2
* @ Author: Anthony_Duan
*/
public class PrimMST<Weight extends Number & Comparable> {

//图的引用
private WeightedGraph G;
//最小索引堆,算法辅助数据结构
private IndexMinHeap<Weight> ipq;
//访问的点所对应的边,算法辅助数据结构
private Edge<Weight>[] edgeTo;
//标记数组,在算法运行过程中标记节点i是否被访问
private boolean[] marked;
//最小生成树所包含的所有边
private Vector<Edge<Weight>> mst;
//最小生成树的权值
private Number mstWeight;


//构造函数,使用Prim算法囚徒的最小生成树
public PrimMST(WeightedGraph graph) {
G = graph;
assert (graph.E() >= 1);
ipq = new IndexMinHeap<Weight>(graph.V());


//算法初始化
marked = new boolean[G.V()];
edgeTo = new Edge[G.V()];
for (int i = 0; i < G.V(); i++) {
marked[i] = false;
edgeTo[i] = null;
}

mst = new Vector<Edge<Weight>>();

//Prim
visit(0);
while (!ipq.isEmpty()) {
//使用最小索引堆找出已经访问的边中权值最小的边
//最小索引堆中存储的是点的索引,通过点的索引找到相应的边
int v = ipq.extractMinIndex();
assert (edgeTo[v] != null);
mst.add(edgeTo[v]);
visit(v);
}

//计算最小生成树的权值
mstWeight = mst.elementAt(0).wt();
for (int i = 1; i < mst.size(); i++) {
mstWeight = mstWeight.doubleValue()+mst.elementAt(i).wt().doubleValue();
}
}


//访问节点v
void visit(int v) {

assert !marked[v];

marked[v] = true;

//将和节点v相连的未访问的另一端点,和与之相连接的边,放入最小堆中
for (Object item : G.adj(v)) {
Edge<Weight> e = (Edge<Weight>) item;
int w = e.other(v);
//如果边的另一端点未被访问
if (!marked[w]) {
//如果从没有考虑过这个端点,直接将这个端点和与之对应的边加入索引堆
if (edgeTo[w] == null) {
edgeTo[w] = e;
ipq.insert(w, e.wt());
}
//如果曾经考虑过这个端点,但现在的边比之前考虑的边更短,则进行替换
else if (e.wt().compareTo(edgeTo[w].wt()) < 0) {
edgeTo[w] = e;
ipq.change(w, e.wt());
}
}
}
}

//返回最小生成树的所有边
Vector<Edge<Weight>> mstEdges() {
return mst;
}

//返回最小生成树的权值
Number result() {
return mstWeight;
}


// 测试 Prim
public static void main(String[] args) {

String filename = "/Users/duanjiaxing/IdeaProjects/Algorithm/src/MinimumSpanTrees/testG1.txt";
int V = 8;

SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

// Test Prim MST
System.out.println("Test Prim MST:");
PrimMST<Double> primMST = new PrimMST<Double>(g);
Vector<Edge<Double>> mst = primMST.mstEdges();
for( int i = 0 ; i < mst.size() ; i ++ ) {
System.out.println(mst.elementAt(i));
}
System.out.println("The MST weight is: " + primMST.result());

System.out.println();
}
}
KruskalMST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package MinimumSpanTrees;

import java.util.Vector;

/**
* @ Description:Kruskal方法求最小生成树
* @ Date: Created in 11:04 2018/8/2
* @ Author: Anthony_Duan
*/
public class KruskalMST <Weight extends Number & Comparable>{

//最小生成树所包含的所有的边
private Vector<Edge<Weight>> mst;

//最小生成树的权值
private Number mstWeight;

//构造函数,使用Kruskal算法graph的最小生成树
public KruskalMST(WeightedGraph graph) {
mst = new Vector<Edge<Weight>>();

//将图中的所有边存放到一个最小堆中
MinHeap<Edge<Weight>> pq = new MinHeap<Edge<Weight>>(graph.E());

for (int i = 0; i < graph.V(); i++) {
for (Object item :
graph.adj(i)) {
Edge<Weight> e = (Edge<Weight>) item;
if (e.v() <= e.w()) {
pq.insert(e);
}
}
}

//创建一个并查集,来查看已经访问的节点的联通情况
UnionFind uf = new UnionFind(graph.V());
while (!pq.isEmpty() && mst.size() < graph.V() - 1) {

//从最小堆中一次从小到大取出所有的边
Edge<Weight> e = pq.extractMin();

//如果该边的两个端点是连通的,说明加入该边将产生环,所以扔掉这条边
if (uf.isConnected(e.v(), e.w())) {
continue;
}

//否则,将这条边添加到最小生成树,同时标记边的两个端点连通
mst.add(e);
uf.unionElements(e.v(), e.w());
}

//计算最小生成树的权值
mstWeight = mst.elementAt(0).wt();
for (int i = 1; i <mst.size() ; i++) {
mstWeight = mstWeight.doubleValue()+mst.elementAt(i).wt().doubleValue();
}
}
// 返回最小生成树的所有边
Vector<Edge<Weight>> mstEdges(){
return mst;
}

// 返回最小生成树的权值
Number result(){
return mstWeight;
}
// 测试 Kruskal
public static void main(String[] args) {

String filename = "/Users/duanjiaxing/IdeaProjects/Algorithm/src/MinimumSpanTrees/testG1.txt";
int V = 8;

SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

// Test Kruskal
System.out.println("Test Kruskal:");
KruskalMST<Double> kruskalMST = new KruskalMST<Double>(g);
Vector<Edge<Double>> mst = kruskalMST.mstEdges();
for( int i = 0 ; i < mst.size() ; i ++ ) {
System.out.println(mst.elementAt(i));
}
System.out.println("The MST weight is: " + kruskalMST.result());

System.out.println();
}
}
-------------End Of This ArticleThank You For Reading-------------