假设共有六个定点,分别为命名为0-5,如下图所示:
根据图构建出邻接矩阵adjMat[][]如下(其中权值为-1表示该两点之间没有路径):
adjMat[][] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | -1 | 6 | -1 | 4 | -1 | -1 |
1 | 6 | -1 | 10 | 7 | 7 | -1 |
2 | -1 | 10 | -1 | 8 | 5 | 6 |
3 | -1 | 7 | 8 | -1 | 12 | -1 |
4 | -1 | -1 | 12 | -1 | -1 | 7 |
5 | -1 | -1 | -1 | -1 | 7 | -1 |
优先级队列以数组的方式来实现,并且数组中,权值越大的值数组下标越小。
首先从任意节点开始,这里从0节点开始,通过搜索邻接矩阵找到0节点到所有相连接点的边:
1、以当前节点号为行遍历该行所有的列,其中列不能等于行号(行号等于列号时代表自身与自身相连)。
2、当该行上某一列的值部不为-1时,代表该点与行值点相连,该值为连线的权值。该案例中邻接矩阵adjMat[0][1]和adjMat[0][3]两个元素不为-1。
3、将该边放在优先级队列中。本案例中队列中有边0-1(6)、0-3(4)。
4、得到优先级队列中的队首元素(即队列中权值最小的元素)并将该边从队列中删除,这里为边0-3。
5、以该边的终点作为新的顶点,这里即边0-3中3为新的顶点。重复以上五个步骤。
注意并不是要把搜索的到邻接矩阵中所有非-1边都放入队列中的,必须符合以下条件的才能进入队列:
1、边的的起点和终点不能是同一个。
2、边的终点已经在树中。
3、边的权值不为-1。
1、顶点类Vertex
顶点类具有两个属性,顶点的名字和改点是否已经在树中
class Vertex {
public char label;
public boolean isInTree;
public Vertex(char label) {
this.label = label;
isInTree = false;
}
}
2、边类Edge
边类包含三个属性,分别时起点srcVert、终点destVert、边的权重这里用distance表示。
class Edge {
// starting edge
public int srcVert;
// ending edge
public int destVert;
public int distance;
public Edge(int srcVert, int destVert, int distance) {
this.srcVert = srcVert;
this.destVert = destVert;
this.distance = distance;
}
}
3、优先级队列类PriorityQ
优先级队列采用数组queArray实现,实现方法比较简单但不严谨,比如队列只能朝一个方向插入,抽取。因此打算专门写一篇文章讨论队列的实现,这里不做重点讨论。并且优先级队列还可以用堆来实现。
队列中用size来标示队列中的元素个数。
队列中包含insert()、removeMin()、peekMin()、peekN()、find()等方法。
insert()用来队列的插入,将权值小的放在队列的首部,即放在数组下标较大的位置;
removeMin()是移除队队首元素即分析部分中所描述的步骤4;
peekMin()用来查看队首元素;
peekN()用来查看队列中下表为n的元素;
find()用来查找边的终点是否已经包含在队列中。
class PriorityQ {
private final int SIZE = 20;
private Edge[] queArray;
private int size;
public PriorityQ() {
queArray = new Edge[SIZE];
size = 0;
}
public void insert(Edge item) {
int j;
for (j = 0; j < size; j++) {
if (item.distance >= queArray[j].distance) {
break;
}
}
for (int k = size - 1; k >= j; k--) {
queArray[k+1] = queArray[k];
}
queArray[j] = item;
size++;
}
public Edge removeMin() {
return queArray[--size];
}
public void removeN(int n) {
for (int j = n; j < size - 1; j++) {
queArray[j] = queArray[j + 1];
}
size--;
}
public Edge peekMin() {
return queArray[size--];
}
public int size() {
return size;
}
public boolean isEmpty() {
return (size == 0);
}
public Edge peekN(int n) {
return queArray[n];
}
public int find(int findDex) {
for (int j = 0; j < size; j++) {
if (queArray[j].destVert == findDex) {
return j;
}
}
return -1;
}
}
4、图类Graph
图类中包含邻接矩阵adjMat[][]、顶点数组列表vertexList[]、优先级队列thePQ等,包含mstw()和putInPQ()两个重要的方法
mstw()是实现最小生成树的核心代码:
从顶点0开始搜索,当树中的元素个数和顶点的个数相同时while循环结束算法退出。
按照分析中所述的方法,一次遍历每一行的每一个值,当遇到符合条件的边时调用putInPQ()方法进行队列插入;
然后从优先级队列中取出队收的边元素,以改变元素的终点作为新的顶点,进行下一步。
putInPQ()时队列执行插入的方法
要插入的边的终点不在队列中是,表示这是一个新的节点,因此直接将该边插入队列中,当该边的终点已经在队列中时,查找出队列中到达该终点的旧边,查看旧边的权值,如果旧边的权值比新边的权值大那么将旧的边删除,将新的边插入,否者新的边不再插入队列。解释为如果又发现了一条到达节点的新路径,如果新路径权值比原来的路径权值要小那么就采用新的路径,否者沿用旧的路径。
class Graph {
private final int MAX_VERTS = 8;
private final int INIFITY = -1;
private Vertex[] vertexList;
private int adjMat[][];
private int nVerts;
private int currentVert;
private PriorityQ thePQ;
private int nTree;
public Graph() {
vertexList = new Vertex[MAX_VERTS];
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for (int i = 0; i < MAX_VERTS; i++) {
for (int j = 0; j < MAX_VERTS; j++) {
adjMat[i][j] = INIFITY;
}
}
thePQ = new PriorityQ();
}
public void addVertex(char lab) {
vertexList[nVerts++] = new Vertex(lab);
}
public void addEdge(int start, int end, int weight) {
adjMat[start][end] = weight;
adjMat[end][start] = weight;
}
public void displayVertex(int v) {
System.out.println(vertexList[v].label);
}
public void mstw() {
currentVert = 0;
while (nTree < nVerts - 1) {
vertexList[currentVert].isInTree = true;
nTree++;
for (int j = 0; j < nVerts; j++) {
if (j == currentVert) {
continue;
}
if (vertexList[j].isInTree) {
continue;
}
int distance = adjMat[currentVert][j];
if (distance == INIFITY) {
continue;
}
putInPQ(j, distance);
}
if (thePQ.size() == 0) {
System.out.println("GRAPH NOT CONNECTED");
return;
}
Edge theEdge = thePQ.removeMin();
int sourceVert = theEdge.srcVert;
currentVert = theEdge.destVert;
System.out.print(vertexList[sourceVert].label);
System.out.print(vertexList[currentVert].label);
System.out.print(" ");
}
for (int i = 0; i < nVerts; i++) {
vertexList[i].isInTree = false;
}
}
public void putInPQ(int newVert, int newDist) {
int queueIndex = thePQ.find(newVert);
if (queueIndex != -1) {
Edge tempEdge = thePQ.peekN(queueIndex);
int oldDist = tempEdge.distance;
if (oldDist > newDist) {
thePQ.removeN(queueIndex);
Edge theEdge = new Edge(currentVert, newVert, newDist);
thePQ.insert(theEdge);
}
} else {
Edge theEdge = new Edge(currentVert, newVert, newDist);
thePQ.insert(theEdge);
}
}
}
程序测试:
public class MSTWApp {
public static void main(String[] args) {
Graph theGraph = new Graph();
theGraph.addVertex('A');
theGraph.addVertex('B');
theGraph.addVertex('C');
theGraph.addVertex('D');
theGraph.addVertex('E');
theGraph.addVertex('F');
theGraph.addEdge(0,1,6);
theGraph.addEdge(0,3,4);
theGraph.addEdge(1,2,10);
theGraph.addEdge(1,3,7);
theGraph.addEdge(1,4,7);
theGraph.addEdge(2,3,8);
theGraph.addEdge(2,4,5);
theGraph.addEdge(2,5,6);
theGraph.addEdge(3,4,12);
theGraph.addEdge(4,5,7);
System.out.println("Minimum spanning tree : ");
theGraph.mstw();
System.out.println();
}
}
结果为:
Minimum spanning tree :
AD AB BE EC CF