普里姆算法是用来求加权连通图中的最小(代价)生成树的算法。
普里姆算法的基本思想是:从图中任意取出一个顶点,把它当成一棵树,然后从与这棵树相接的边中选取一条权值最小的边,并将这条边及其所连接的顶点一同并入这棵树中,重复以上操作,直到所有的顶点都被并入到这棵树中为止。
容易看到,这里有一个递归重复进行的过程,所以可以采用递归的思想来完成该算法。
首先,需要构造一个图结构,本文以如下图为图结构
图的顶点定义如下:
import java.util.ArrayList;
import java.util.List;
//表示图的顶点
public class GraphNode {
// 表示该顶点的数据
private String data;
// 表示该顶点的边集合
public List<GraphEdge> nodeList = null;
// 表示该顶点是否被访问过
private boolean isVisited;
// 构造方法
public GraphNode(String data) {
this.data = data;
isVisited = false;
if (nodeList == null) {
nodeList = new ArrayList<>();
}
}
public String getData() {
return data;
}
public List<GraphEdge> getEdgeList() {
return nodeList;
}
public void setVisited(boolean isVisited) {
this.isVisited = isVisited;
}
public boolean isVisited() {
return isVisited;
}
}
图的边定义如下:
// 表示图的边
public class GraphEdge {
// 该边连接的左顶点
private GraphNode leftNode;
// 该边连接的右顶点
private GraphNode rightNode;
// 边的权值
private int weight;
// 该边是否被访问
private boolean isVisited;
public GraphEdge(GraphNode leftNode, GraphNode rightNode, int weight) {
this.leftNode = leftNode;
this.rightNode = rightNode;
this.weight = weight;
isVisited = false;
}
public GraphNode getLeftNode() {
return leftNode;
}
public GraphNode getRightNode() {
return rightNode;
}
public int getWeight() {
return weight;
}
public boolean isVisited() {
return isVisited;
}
public void setVisited(boolean isVisited) {
this.isVisited = isVisited;
}
}
构造图的代码放在main函数中:
public static void main(String[] args) {
GraphNode A = new GraphNode("A");
GraphNode B = new GraphNode("B");
GraphNode C = new GraphNode("C");
GraphNode D = new GraphNode("D");
GraphNode E = new GraphNode("E");
GraphEdge AB = new GraphEdge(A, B, 2);
GraphEdge AC = new GraphEdge(A, C, 4);
GraphEdge AD = new GraphEdge(A, D, 2);
GraphEdge BC = new GraphEdge(B, C, 3);
GraphEdge CD = new GraphEdge(C, D, 3);
GraphEdge BE = new GraphEdge(B, E, 3);
GraphEdge CE = new GraphEdge(C, E, 4);
GraphEdge DE = new GraphEdge(D, E, 5);
A.getEdgeList().add(AB);
A.getEdgeList().add(AC);
A.getEdgeList().add(AD);
B.getEdgeList().add(AB);
B.getEdgeList().add(BC);
B.getEdgeList().add(BE);
C.getEdgeList().add(AC);
C.getEdgeList().add(BC);
C.getEdgeList().add(CD);
C.getEdgeList().add(CE);
D.getEdgeList().add(AD);
D.getEdgeList().add(CD);
D.getEdgeList().add(DE);
E.getEdgeList().add(BE);
E.getEdgeList().add(CE);
E.getEdgeList().add(DE);
}
这段代码可以完成图的构建。
接下来是普里姆算法的代码:
static int sum = 0;
static void prim(List<GraphNode> nodes, int size) {
if (nodes.size() == size)
return;
int tmp = Integer.MAX_VALUE;
int x = -1, y = -1;
for (int i = 0; i < nodes.size(); i++) {
List<GraphEdge> edges = nodes.get(i).getEdgeList();
for (int j = 0; j < edges.size(); j++) {
GraphEdge ge = edges.get(j);
if (ge.isVisited() == true)
continue;
if (ge.getWeight() <= tmp) {
tmp = ge.getWeight();
x = i;
y = j;
}
}
}
GraphEdge ge = nodes.get(x).getEdgeList().get(y);
ge.setVisited(true);
sum += ge.getWeight();
if (!ge.getLeftNode().isVisited()) {
nodes.add(ge.getLeftNode());
ge.getLeftNode().setVisited(true);
}
if (!ge.getRightNode().isVisited()) {
nodes.add(ge.getRightNode());
ge.getRightNode().setVisited(true);
}
prim2(nodes, size);
}
sum用来求最后生成的最小生成树的权值和。
prim()方法就是实现普里姆算法的代码,这个方法接收一个list,这个list中的点就是每一次生成的树包含的所有节点
(1)初始值只有一个节点:出发节点。出发节点可以任选
(2)然后prim()方法就是从接收的list中遍历所有节点的所有相接的边,选取权值最小的一条,如果权值最小有多条,则任意选一条即可。
(3)选定权值最小的那条边后,就把这条边的另一个节点加到list中并设置状态已被访问,然后设置该边也已被访问
(4)这时,list中就多了一个节点,递归调用prim()方法即可。递归的终止条件就是list中包含了所有的节点,也就是代码中的nodes.size() == size。
完成代码详见:
https://github.com/ChenSmallJian/algorithm/tree/master/%E6%99%AE%E9%87%8C%E5%A7%86%E7%AE%97%E6%B3%95