学习图相关的算法(Java 实现)(2)——Prim算法求最小生成树
也不知道为啥要写博客。。。
相关定义
由于太饿了,相关定义(加权图、生成树、最小生成树)已经被吃掉了,所以麻烦您自行搜索一下哈(~ ̄▽ ̄)~
下面就直奔主题
最小生成树
先来看一个简单的情况,两个节点的最小生成树(最小生成树定义允许存在权重为负的边),毫无疑问要选权重为-1的边。
接下来换一张节点多一些的图:
看到桌子上出现了一些头发?不要着急,你的好友小明已经帮你将左边三个节点 {1, 5, 6} (粉色节点)组成的子图和右边三个节点 {2, 3, 4} (黄色节点)组成的子图分别找到了最小生成树,你只需要将它们连接成一棵最小生成树
不妨将左边子图的最小生成树和右边子图的最小生成树分别看做一个节点
显然,应该选择权重为-1的边加入最小生成树。
Prim算法
假设目前已经有左边子图的最小生成树了,如下图
接下来,便可以在连接左边子图与右边节点的所有边中,选择权重最小的一条边,将其加入左边的最小生成树中,同时,该边连接的节点也加入了最小生成树,显然还是权重为-1的边(怎么总是它?),如下图
那么接下来就是如何用算法实现了:
-
数据结构
-节点:boolean数组marked[]表示每个节点是否在最小生成树中
-边:队列mst 保存最小生成树中的边
-优先队列:MinPQ 根据权重比较边 -
算法
- 将节点0加入最小生成树中(marked[0]标记为true),与节点0相邻的所有边加入优先队列MinPQ
- 从优先队列MinPQ中找到权重最小且与该边相邻的一个节点不在最小生成树中的边
- 将该边加入最小生成树mst,并标记该边相邻的未被标记的节点
- 将该节点相邻的所有与该边相邻的一个节点不在最小生成树中的边加入优先队列
- 重复2, 3, 4直到所有节点被标记
// 代码直接照着《算法4》敲的(这样真的好吗?)
import java.util.*;
public class Prim {
private boolean[] marked;
private Queue<Edge> mst;
private PriorityQueue<Edge> pq;
public Prim(EdgeWeightedGraph G) {
mst = new LinkedList<>();
pq = new PriorityQueue<>();
marked = new boolean[G.V()];
visit(G, 0);
while(!pq.isEmpty()) {
Edge e = pq.poll();
int v = e.either(), w = e.other(v);
if(marked[v] && marked[w]) {
continue;
}
mst.add(e);
if(!marked[v]) {
visit(G, v);
}
else {
visit(G, w);
}
}
}
private void visit(EdgeWeightedGraph G, int v) {
marked[v] = true;
for(Edge e: G.adj(v))
if(!marked[e.other(v)]) {
pq.add(e);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
class Edge implements Comparable{
private final int v; // 顶点1
private final int w; // 顶点2
private final double weight; // 边的权重
public Edge(int v, int w, double weight) {
super();
this.v = v;
this.w = w;
this.weight = weight;
}
public double weight() {
return weight;
}
public int either() {
return v;
}
public int other(int vertex) {
return v == vertex? w: v;
}
@Override
public int compareTo(Object o) {
Edge that = (Edge)o;
if(this.weight() < that.weight())
return -1;
if(this.weight() > that.weight())
return 1;
return 0;
}
public String toString() {
return String.format("%d-%d %.2f", v, w, weight);
}
}
class EdgeWeightedGraph {
private final int V;
private int E;
private LinkedList<Edge>[] adj;
public EdgeWeightedGraph(int V) {
this.V = V;
this.E = 0;
adj = new LinkedList[V];
for(int i = 0; i < V; ++i) {
adj[i] = new LinkedList<Edge>();
}
}
public int V() {
return V;
}
public int E() {
return E;
}
public void addEdge(Edge e) {
int v = e.either(), w = e.other(v);
adj[v].add(e);
adj[w].add(e);
E++;
}
public LinkedList<Edge> adj(int v){
return adj[v];
}
public LinkedList<Edge> edges(){
LinkedList<Edge> list = new LinkedList<Edge>();
for(int v = 0; v < V; ++v) {
for(Edge e: adj[v]) {
if(e.other(v) > v) {
list.add(e);
}
}
}
return list;
}
}
Kruskal算法
反正应该也不会有人看,所以咕咕咕~~