《算法4》最小生成树3种算法原理:
Prime算法(延时实现):将mst里面顶点的邻接边集合的最小边加入最小生成树mst,并延迟删除2个顶点都已经标记的边;
Prime算法(即时实现):距离最近的顶点加到最小生成树mst里面,并修改距离w顶点到最小生成树mst的的距离;
Kruskal:将邻接边集合的最小边加入最小生成树mst,加入的边不形成环(union-find并查集算法检测);
各种最小生成树算法的性能特点
demo中使用的测试数据,来自《算法4》,配图这里有个小的错误,看的时候注意一下。
用到的数据结构:
邻接边类 Edge.java
/**
* 边的对象
*
* @author ltx
*/
public class Edge implements Comparable<Edge> {
public int v; //顶点v
public int w; //顶点w
public double weight; //权重
public Edge(int v, int w, double weight) {
this.v = v;
this.w = w;
this.weight = weight;
}
/**
* 边的另外一个顶点
*
* @param v 顶点v
* @return
*/
public int other(int v) {
if (this.v == v) {
return this.w;
} else if (this.w == v) {
return this.v;
}
throw new RuntimeException("边不对!");
}
/**
* 实现compareTo方法,集合排序用
*
* @param that
* @return
*/
@Override
public int compareTo(Edge that) {
if (this.weight > that.weight) {
return 1;
} else if (this.weight < that.weight) {
return -1;
}
return 0;
}
@Override
public String toString() {
return "Edge{" +
"v=" + v +
", w=" + w +
", weight=" + weight +
'}';
}
}
加权无向图类 EdgeWeightedGraph.java
/**
* 加权无向图
*
* @author ltx
*/
public class EdgeWeightedGraph {
public int V; //顶点数量
public int E; //边数量
public List<Edge>[] adj; //邻接边
/**
* 初始化加权无向图
*
* @param V 顶点数
*/
public EdgeWeightedGraph(int V) {
//初始化顶点数
this.V = V;
//初始化边数
this.E = 0;
//初始化邻接表
this.adj = new List[V];
for (int i = 0; i < V; i++) {
adj[i] = new ArrayList<>();
}
}
/**
* 添加边
*
* @param e 边
*/
public void addEdge(Edge e) {
adj[e.v].add(e);
adj[e.w].add(e);
E++;
}
/**
* 打印邻接表结构
*/
public void show() {
System.out.printf("%d 个顶点, %d 条边\n", V, E);
for (int i = 0; i < V; i++) {
System.out.println(i + ": " + adj[i]);
}
}
/**
* 初始化加权无向图
*
* @return
*/
public static EdgeWeightedGraph init() {
/**
* 算法4里面的加权无向图
*/
EdgeWeightedGraph edgeWeightedGraph = new EdgeWeightedGraph(8);
edgeWeightedGraph.addEdge(new Edge(4, 5, 0.35));
edgeWeightedGraph.addEdge(new Edge(4, 7, 0.37));
edgeWeightedGraph.addEdge(new Edge(5, 7, 0.28));
edgeWeightedGraph.addEdge(new Edge(0, 7, 0.16));
edgeWeightedGraph.addEdge(new Edge(1, 5, 0.32));
edgeWeightedGraph.addEdge(new Edge(0, 4, 0.38));
edgeWeightedGraph.addEdge(new Edge(2, 3, 0.17));
edgeWeightedGraph.addEdge(new Edge(1, 7, 0.19));
edgeWeightedGraph.addEdge(new Edge(0, 2, 0.26));
edgeWeightedGraph.addEdge(new Edge(1, 2, 0.36));
edgeWeightedGraph.addEdge(new Edge(1, 3, 0.29));
edgeWeightedGraph.addEdge(new Edge(2, 7, 0.34));
edgeWeightedGraph.addEdge(new Edge(6, 2, 0.40));
edgeWeightedGraph.addEdge(new Edge(3, 6, 0.52));
edgeWeightedGraph.addEdge(new Edge(6, 0, 0.58));
edgeWeightedGraph.addEdge(new Edge(6, 4, 0.93));
return edgeWeightedGraph;
}
}
一、Prime算法(延时实现)
最小生成树的Prime算的延时实现类 LazyPrimeMST.java
/**
* 最小生成树的Prime算法(延时实现)
* 原理-将mst里面顶点的邻接边集合的最小边加入最小生成树mst,并延迟删除2个顶点都已经标记的边
*
* @author ltx
*/
public class LazyPrimeMST {
private EdgeWeightedGraph graph;
private boolean marked[];//标记已经在最小生成树mst里面的顶点
private List<Edge> pq; //顺序排列的所有边
public List<Edge> mst; //最小生成树mst所有的边
public double weights; //最小生成树mst的权重合计
/**
* 初始化最小生成树mst
*
* @param graph 加权无向图
*/
public LazyPrimeMST(EdgeWeightedGraph graph) {
this.graph = graph;
marked = new boolean[graph.V];
mst = new ArrayList<>();
pq = new ArrayList<>();
//从顶点0开始
visit(0);
while (!pq.isEmpty()) {
//按权重排个序
Collections.sort(pq);
//权重最小边
Edge minE = pq.remove(0);
//如果边的两个顶点都被标记了,就跳过,继续拿下一条最小边
if (marked[minE.v] && marked[minE.w]) {
continue;
}
//边加到最小生成树mst里面
mst.add(minE);
//权重合计
weights += minE.weight;
//未标记的顶点
if (!marked[minE.v]) {
visit(minE.v);
}
if (!marked[minE.w]) {
visit(minE.w);
}
}
}
/**
* 标记顶点v,并将顶点v的边加入队列中
*
* @param v 顶点
*/
private void visit(int v) {
//标记
marked[v] = true;
//将含有顶点v,且另一个顶点未被标记的边全部加到队列里面
for (Edge e : graph.adj[v]) {
if (!marked[e.other(v)]) {
pq.add(e);
}
}
}
}
二、Prime算法(即时实现)
顶点和权重类 Vertex.java
/**
* 顶点和权重
*
* @author ltx
*/
public class Vertex implements Comparable<Vertex> {
public int v; //顶点v
public double weight; //顶点w距离最小生成树mst的权重
public Vertex(int v, double weight) {
this.v = v;
this.weight = weight;
}
/**
* 实现compareTo方法,集合排序用
*
* @param that
* @return
*/
@Override
public int compareTo(Vertex that) {
if (this.weight > that.weight) {
return 1;
} else if (this.weight < that.weight) {
return -1;
}
return 0;
}
/**
* 重写equals
*
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Vertex vertex = (Vertex) o;
return v == vertex.v;
}
@Override
public int hashCode() {
return Objects.hash(v);
}
@Override
public String toString() {
return "Vertex{" +
"v=" + v +
", weight=" + weight +
'}';
}
}
最小生成树的Prime算的即时实现类 PrimeMST.java
/**
* 最小生成树的Prime算法(即时实现)
* 原理-距离最近的顶点加到最小生成树mst里面,并修改距离w顶点到最小生成树mst的的距离
*
* @author ltx
*/
public class PrimeMST {
private EdgeWeightedGraph graph;
private Edge[] edgeTo; //实时动态存-顶点w离最小生成树mst最小的边
private double[] distTo; //实时动态存-顶点w离最小生成树mst最小的边的权重
private boolean marked[];//标记已经在最小生成树mst里面的顶点
private List<Vertex> pq; //顶点队列
public List<Edge> mst; //最小生成树mst所有的边
public double weights; //最小生成树mst的权重合计
/**
* 初始化最小生成树mst
*
* @param graph 加权无向图
*/
public PrimeMST(EdgeWeightedGraph graph) {
this.graph = graph;
edgeTo = new Edge[graph.V];
distTo = new double[graph.V];
//将顶点v到mst的权重初始化为最大
for (int i = 0; i < graph.V; i++) {
//正无穷的常数
distTo[i] = Double.POSITIVE_INFINITY;
}
marked = new boolean[graph.V];
pq = new LinkedList<>();
mst = new ArrayList<>();
//从顶点0开始
pq.add(new Vertex(0, 0));
while (!pq.isEmpty()) {
//按权重排个序
Collections.sort(pq);
Vertex minV = pq.remove(0);
//离mst最近的顶点
visit(minV.v);
}
//权重合计-从1开始,排除0起点
for (int i = 1; i < graph.V; i++) {
weights += distTo[i];
}
//mst-从1开始,排除0起点初始值
for (int i = 1; i < graph.V; i++) {
mst.add(edgeTo[i]);
}
}
/**
* @param v 顶点v
*/
private void visit(int v) {
//标记
marked[v] = true;
//将含有顶点v,且另一个顶点未被标记的边全部加到队列里面
for (Edge e : graph.adj[v]) {
int w = e.other(v);
//另一个顶点在mst中则跳过
if (marked[w]) {
continue;
}
if (e.weight < distTo[w]) {
//顶点w到mst的最小边-因为是实时修改的
edgeTo[w] = e;
//修改顶点w到mst的距离-修改为最小
distTo[w] = e.weight;
//如果队列里面有顶点w,则修改它到mst的距离权重
//Vertex重写了equals,可直接用w对比
Vertex node = new Vertex(w, e.weight);
if (pq.contains(node)) {
Vertex temp = pq.get(pq.indexOf(node));
temp.weight = e.weight;
} else {
//没有就新增
pq.add(node);
}
}
}
}
}
三、Kruskal算法
这里使用到union-find并查集算法,检测顶点是否在连通分量里面,具体算法可以查看之前的文章《《算法4》union-find并查集算法 (quick-find | quick-union | 加权quick-union | 路径压缩的加权quick-union)》
最小生成树的Kruskal算法实现类 KruskalMST.java
/**
* 最小生成树的Kruskal算法
* 原理-将邻接边集合的最小边加入最小生成树mst,加入的边不形成环(union-find并查集算法检测)
*
* @author ltx
*/
public class KruskalMST {
private EdgeWeightedGraph graph;
public List<Edge> mst; //最小生成树mst所有的边
public double weights; //最小生成树mst的权重合计
/**
* 初始化最小生成树mst
*
* @param graph 加权无向图
*/
public KruskalMST(EdgeWeightedGraph graph) {
this.graph = graph;
mst = new ArrayList<>();
//所有的边放pq中
List<Edge> pq = new ArrayList<>();
for (int i = 0; i < graph.V; i++) {
for (Edge e : graph.adj[i]) {
pq.add(e);
}
}
//初始化并查集 union-find并查集算法(加权 quick-union)
UnionFind un = new WeightedQuickUnion(graph.V);
//边数==顶点数-1时候就结束,已经找到mst了
while (!pq.isEmpty() && mst.size() < graph.V - 1) {
//按权重排个序
Collections.sort(pq);
//权重最小边
Edge minE = pq.remove(0);
//如果已经在一个连通分量里面(union-find并查集算法),就跳过,继续拿下一条最小边
if (un.connected(minE.v, minE.w)) {
continue;
}
//边加到最小生成树mst里面
mst.add(minE);
//权重合计
weights += minE.weight;
//将顶点v和w放一个连通分量里面 union-find并查集算法(加权 quick-union)
un.union(minE.v, minE.w);
}
}
}
测试方法:
@Test
void mst() {
System.out.println("################加权无向图################");
EdgeWeightedGraph graph = EdgeWeightedGraph.init();
graph.show();
System.out.println("################最小生成树mst################");
System.out.println("-------Prime算法(延时实现)-------");
LazyPrimeMST lazyPrimeMST = new LazyPrimeMST(graph);
System.out.println(lazyPrimeMST.mst);
System.out.printf("最小生成树mst权重合计: %.2f\n", lazyPrimeMST.weights);
System.out.println("-------Prime算法(即时实现)-------");
PrimeMST primeMST = new PrimeMST(graph);
System.out.println(primeMST.mst);
System.out.printf("最小生成树mst权重合计: %.2f\n", primeMST.weights);
System.out.println("-------Kruskal算法-------");
KruskalMST kruskalMST = new KruskalMST(graph);
System.out.println(kruskalMST.mst);
System.out.printf("最小生成树mst权重合计: %.2f\n", kruskalMST.weights);
}
测试结果:
################加权无向图################
8 个顶点, 16 条边
0: [Edge{v=0, w=7, weight=0.16}, Edge{v=0, w=4, weight=0.38}, Edge{v=0, w=2, weight=0.26}, Edge{v=6, w=0, weight=0.58}]
1: [Edge{v=1, w=5, weight=0.32}, Edge{v=1, w=7, weight=0.19}, Edge{v=1, w=2, weight=0.36}, Edge{v=1, w=3, weight=0.29}]
2: [Edge{v=2, w=3, weight=0.17}, Edge{v=0, w=2, weight=0.26}, Edge{v=1, w=2, weight=0.36}, Edge{v=2, w=7, weight=0.34}, Edge{v=6, w=2, weight=0.4}]
3: [Edge{v=2, w=3, weight=0.17}, Edge{v=1, w=3, weight=0.29}, Edge{v=3, w=6, weight=0.52}]
4: [Edge{v=4, w=5, weight=0.35}, Edge{v=4, w=7, weight=0.37}, Edge{v=0, w=4, weight=0.38}, Edge{v=6, w=4, weight=0.93}]
5: [Edge{v=4, w=5, weight=0.35}, Edge{v=5, w=7, weight=0.28}, Edge{v=1, w=5, weight=0.32}]
6: [Edge{v=6, w=2, weight=0.4}, Edge{v=3, w=6, weight=0.52}, Edge{v=6, w=0, weight=0.58}, Edge{v=6, w=4, weight=0.93}]
7: [Edge{v=4, w=7, weight=0.37}, Edge{v=5, w=7, weight=0.28}, Edge{v=0, w=7, weight=0.16}, Edge{v=1, w=7, weight=0.19}, Edge{v=2, w=7, weight=0.34}]
################最小生成树mst################
-------Prime算法(延时实现)-------
[Edge{v=0, w=7, weight=0.16}, Edge{v=1, w=7, weight=0.19}, Edge{v=0, w=2, weight=0.26}, Edge{v=2, w=3, weight=0.17}, Edge{v=5, w=7, weight=0.28}, Edge{v=4, w=5, weight=0.35}, Edge{v=6, w=2, weight=0.4}]
最小生成树mst权重合计: 1.81
-------Prime算法(即时实现)-------
[Edge{v=1, w=7, weight=0.19}, Edge{v=0, w=2, weight=0.26}, Edge{v=2, w=3, weight=0.17}, Edge{v=4, w=5, weight=0.35}, Edge{v=5, w=7, weight=0.28}, Edge{v=6, w=2, weight=0.4}, Edge{v=0, w=7, weight=0.16}]
最小生成树mst权重合计: 1.81
-------Kruskal算法-------
[Edge{v=0, w=7, weight=0.16}, Edge{v=2, w=3, weight=0.17}, Edge{v=1, w=7, weight=0.19}, Edge{v=0, w=2, weight=0.26}, Edge{v=5, w=7, weight=0.28}, Edge{v=4, w=5, weight=0.35}, Edge{v=6, w=2, weight=0.4}]
最小生成树mst权重合计: 1.81