所谓最小生成树,就是用最少的边连接所有的顶点。对于给定的一组顶点,可能又很多种最小生成树,但是最小生成树边E的数量总是比顶点V的数量小1,即E=V-1。
普里姆算法
假设N=(V,{E})是连通网,TE是N上最小生成树中边的集合,算法从 U = u 0 ( u 0 ∈ V ) U={u_0}(u_0∈V) U=u0(u0∈V),TE={}开始。重复执行下述操作:在所有u∈U,v∈V-U的边(u,v)∈E中找到一条代价最小的边( u 0 , v 0 u_0,v_0 u0,v0)并入集合TE,同时 v 0 v_0 v0并入U,直至U = V为止。此时TE中必有n-1条边,则T =(V,{TE})为N的最小生成树。此算法更适用于稠密图。
算法要点如下:
所谓连通图可以为有权图(网)也可以为无权图(把权值均看成1)。
建议用优先级队列来实现这个反复选择最小的路径,而不是链表或数组,这是解决最小生成树的有效方式。在正式的程序中,优先级队列可能基于堆来实现(堆其实是个很简单的数据结构),这会加快在较大的优先级队列中的操作。但是在本例中,我们使用数组实现优先级队列,仅仅为了说明算法。
任意从一个顶点(一般取第一个顶点)开始,把它放入树的集合中,然后重复做下面的事情:
- 找到从最新的顶点到其他顶点的所有边,这些顶点不能在树的集合中,把这些边放入优先级队列中。
- 找出权值最小的边,把它和它所达到的顶点放入树的集合中。
重复这些步骤,直到所有的顶点都在树的集合中,这时工作完成。
Java代码实现
边界路径类,主要记录了边的始末顶点,以及边的权值
class Edge{
public int srcVert;//边起始顶点的索引
public int destVert;//边终止顶点的索引
public int weight;//权重
public Edge(int sv, int dv, int w) {
srcVert = sv;
destVert = dv;
weight = w;
}
}
优先级队列
class PriorityQueue{
private final int SIZE = 20;
public Edge[] queArray;
private int size;
public PriorityQueue() {
queArray = new Edge[SIZE];
size = 0;
}
//有序的插入边
public void insert(Edge edge) {
int i;
//找到插入的位置
for(i = 0; i < size; i ++)
if(edge.weight >= queArray[i].weight )
break;
//插入位置后面的元素往后移一位,即权重比插入边小往后移一位
for(int j = size - 1; j >= i; j -- )
queArray[j + 1] = queArray[j];
//将edge插入腾出的位置
queArray[i] = edge;
size ++;
}
//删除权重最小的边
public Edge removeMin() {
return queArray[--size];
}
//删除n位置的边
public void removeN(int n) {
for(int i = n; i < size - 1; i ++)
queArray[i] = queArray[i + 1];
size --;
}
//返回权重最小的边
public Edge peekMin() {
return queArray[size - 1];
}
//返回n位置的边
public Edge peekN(int n) {
return queArray[n];
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
//寻找特定的目的顶点的索引
public int find(int index) {
for(int i = 0; i < size; i ++)
if(queArray[i].destVert == index)
return i;
return -1;
}
public void display() {
if(size == 0)
return;
System.out.println("size:" + size);
for(int i = 0; i < size; i ++)
System.out.print(queArray[i].srcVert + " " + queArray[i].destVert + " -> ");
System.out.println();
}
}
有权图的最小生成树算法——普里姆算法
public class WeightGraph {
/**
* 存储结构为邻接矩阵
* @author Administrator
*
*/
class Vertex{//顶点类
public String data;
public boolean isVisted;
public boolean isInTree;
public Vertex(String data) {
this.data = data;
isVisted = false;
isInTree = false;
}
}
public final int MAX_VERTEX = 25;//顶点个数最大值
public final int MAX_VALUE = 65535;
public Vertex[] vertexArray;//顶点表
public int[][] edgeArray;//邻接矩阵
public int verNum;//顶点数量
public int curIndex;//当前顶点索引
public PriorityQueue pQueue;//存储边的优先级队列
public int nTree;//生成树中的顶点数量
//初始化图
public WeightGraph() {
vertexArray = new Vertex[MAX_VERTEX];
edgeArray = new int[MAX_VERTEX][MAX_VERTEX];
verNum = 0;
for(int i = 0; i < MAX_VERTEX; i ++)
for(int j = 0; j < MAX_VERTEX; j ++)
edgeArray[i][j] = MAX_VALUE;
pQueue = new PriorityQueue();
}
//增加顶点
public void addVertex(String data) {
vertexArray[verNum ++] = new Vertex(data);
}
//增加边
public void addEdge(int start , int end, int weight) {
edgeArray[start - 1][end - 1] = weight;
edgeArray[end - 1][start - 1] = weight;
}
/**
* 带权图的最小生成树之普里姆算法
* @return
*/
public int minSpanninTreePlim() {
int result = 0;
curIndex = 0;
//当所有顶点未都加入生成树时
while(nTree < verNum) {
vertexArray[curIndex].isInTree = true;
nTree ++;
//在pQueue中加入与当前顶点相连接的边
for(int i = 0; i < verNum; i ++) {
//当前顶点
if(i == curIndex)
continue;
//i顶点已加入生成树
if(vertexArray[i].isInTree)
continue;
//计算当前顶点到顶点i的权重
int weight = edgeArray[curIndex][i];
if(weight == MAX_VALUE)
continue;
//将i顶点加入pQueue中
putInPQ(i, weight);
}
if(pQueue.size() != 0) {
//取出最小边
Edge edge = pQueue.removeMin();
int startVert = edge.srcVert;
curIndex = edge.destVert;
System.out.print(String.format("(%s,%s)",vertexArray[startVert].data, vertexArray[curIndex].data));
if(pQueue.size() != 0)
System.out.print("——>");
result += edge.weight;
}
}
System.out.println();
return result;
}
private void putInPQ(int newVert, int newWeight) {
//判断优先级队列中是否已经存在newVert的边
int queueIndex = pQueue.find(newVert);
//如果有,则与当前顶点到目的顶点的边权重比较,保留小的那个
if(queueIndex != -1) {
Edge edge = pQueue.peekN(queueIndex);
int oldWeight = edge.weight;
if(oldWeight > newWeight) {
//更新优先级队列的边
pQueue.removeN(queueIndex);
Edge newEdge = new Edge(curIndex, newVert, newWeight);
pQueue.insert(newEdge);
//System.out.print(vertexArray[newEdge.srcVert].data + ", " + vertexArray[newEdge.destVert].data + " ");
}
}else {
Edge newEdge = new Edge(curIndex, newVert, newWeight);
pQueue.insert(newEdge);
//System.out.print(vertexArray[newEdge.srcVert].data + ", " + vertexArray[newEdge.destVert].data + " ");
}
}
测试代码
public static void main(String[] args) {
WeightGraph graph = new WeightGraph();
graph.addVertex("a");
graph.addVertex("b");
graph.addVertex("c");
graph.addVertex("d");
graph.addVertex("e");
/**
* a
* / \
* / \
* b ——— d
* / \
* e c
*/
graph.addEdge(1, 2, 2);
graph.addEdge(1, 4, 4);
graph.addEdge(2, 4, 4);
graph.addEdge(2, 5, 5);
graph.addEdge(3, 4, 3);
//System.out.println("最小生成树(克鲁斯卡尔算法)的权重之和:" + graph.minSpanninTreeKruskal());
System.out.println("最小生成树(普利姆算法)的权重之和:" + graph.minSpanninTreePlim());
}
参考文章:图也有权重,你们知道吗?(原创作者: 倪升武 程序员私房菜)