带权图的最小生成树
规则:总是选择权值最低的边。
带权图的最小生成树中有所有的顶点和连接它们的必要的边,且这些边的权值最小。
算法要点
从一个顶点开始,把它放入树的集合中。以下是步骤:
- 找到从最新的顶点到其他顶点的所有边,这些顶点不能在树的集合中。把这些边放入优先级队列。
- 找出权值最小的边,把它和它所到达的顶点放入树的集合中。
重复以上步骤,直到所有顶点都在树的集合中。
Java实现
package com.jikefriend.graph;
public class GraphMstw {
static class Edge {
public int srcVert; //源顶点
public int destVert; //目标顶点
public int distance; //源顶点到目标顶点的距离
public Edge(int sv, int dv, int d) {
srcVert = sv;
destVert = dv;
distance = d;
}
}
/**
* 优先级队列
*/
static 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 i = size - 1; i >= j; i--)
queArray[i + 1] = queArray[i];
queArray[j] = item;
size++;
}
public Edge removeMin() {
return queArray[--size];
}
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];
}
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 i = 0; i < size; i++)
if (queArray[i].destVert == findDex)
return i;
return -1;
}
}
static class Vertex {
public char label;
public boolean isInTree;
public Vertex(char lab) {
label = lab;
isInTree = false;
}
}
static class Graph {
private final int MAX_VERTS = 20;
private final int INFINITY = 1000000;
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] = INFINITY;
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 mstw() {
currentVert = 0;
/** 所有顶点都在树中则结束循环 */
while (nTree < nVerts - 1) {
/** 当前顶点放在书中 */
vertexList[currentVert].isInTree = true;
nTree++;
/** 遍历所有顶点,将和这个顶点连接的边放到优先级队列中 */
for (int i = 0; i < nVerts; i++) {
if (i == currentVert) //源点和终点相同,跳过
continue;
if (vertexList[i].isInTree) //终点如果已经在树中,跳过
continue;
int distance = adjMat[currentVert][i];
if (distance == INFINITY) //如果两个顶点直接没有边,跳过
continue;
putInPQ(i, 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 static void main(String[] args) {
Graph graph = new Graph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addVertex('F');
graph.addEdge(0, 1, 6); //AB 6
graph.addEdge(0, 3, 4); //AD 4
graph.addEdge(1, 2, 10); //BC 10
graph.addEdge(1, 3, 7); //BD 7
graph.addEdge(1, 4, 7); //BE 7
graph.addEdge(2, 3, 8); //CD 8
graph.addEdge(2, 4, 5); //CE 5
graph.addEdge(2, 5, 6); //CF 6
graph.addEdge(3, 4, 12); //DE 12
graph.addEdge(4, 5, 7); //EF 7
System.out.print("Mininum spanning tree: ");
graph.mstw();
System.out.println();
}
}
最短路径问题
最短路径问题可以描述为:对给定的源点和终止点,找出权值最低的一条路线。
最短路径算法的关键是记录源点到其他顶点的最短路径,同时还要记录走过的路径。
无权图的最短路径问题要找到两点间的路径,且路径长度最短。
带权图的最短路径问题产生的路径总权值和最小。
Java实现
package com.jikefriend.graph;
public class GraphPath {
/**
* 用于保存源点到其他顶点的距离,已经顶点的父顶点(用于记录走过的路径)
*/
static class DistPar {
/** 起点到其他顶点的距离 */
public int distance;
/** 顶点的当前父顶点 */
public int parentVert;
public DistPar(int pv, int d) {
distance = d;
parentVert = pv;
}
}
static class Vertex {
public char label;
public boolean isInTree;
public Vertex(char lab) {
label = lab;
isInTree = false;
}
}
static class Graph {
private final int MAX_VERTS = 20;
private final int INFINITY = 1000000;
/** 顶点数组 */
private Vertex[] vertexList;
/** 邻接矩阵 */
private int[][] adjMat;
/** 顶点数量 */
private int nVerts;
/** 树中顶点的数量 */
private int nTree;
/** 总是存储从源点到所有已知顶点的当前最短路径 */
private DistPar[] sPath;
/** 当前顶点 */
private int currentVert;
/** 源点到当前顶点的权值(距离) */
private int startToCurrent;
public Graph() {
vertexList = new Vertex[MAX_VERTS];
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
nTree = 0;
for (int i = 0; i < MAX_VERTS; i++)
for (int j = 0; j < MAX_VERTS; j++)
adjMat[i][j] = INFINITY;
sPath = new DistPar[MAX_VERTS];
}
public void addVertex(char lab) {
vertexList[nVerts++] = new Vertex(lab);
}
public void addEdge(int start, int end, int weight) {
adjMat[start][end] = weight;
}
public void path() {
int startTree = 0; //从起点开始
vertexList[startTree].isInTree = true; //将起点放入树中
nTree = 1;
/** 把邻接矩阵的对应边的权值放入sPath[]数组中 */
for (int i = 0; i < nVerts; i++) {
int tempDist = adjMat[startTree][i];
sPath[i] = new DistPar(startTree, tempDist);
}
while (nTree < nVerts) {
/** 选择最小权值 */
int indexMin = getMin();
int minDist = sPath[indexMin].distance;
if (minDist == INFINITY) {
System.out.println("There are unreachable vertices");
break;
} else {
/** 如果最小权值的顶点从源点可达,则更新当前顶点 */
currentVert = indexMin;
/** sPath[]数组的当前项的权值 */
startToCurrent = sPath[indexMin].distance;
}
/** 把最小权值对应的顶点放入树中 */
vertexList[currentVert].isInTree = true;
nTree++;
/** 根据当前顶点的变化更新sPath[]数组 */
adjust_sPath();
}
display();
nTree = 0;
for (int i = 0; i < nVerts; i++)
vertexList[i].isInTree = false;
}
private int getMin() {
int minDist = INFINITY;
int indexMin = 0;
for (int i = 0; i < nVerts; i++) {
if (!vertexList[i].isInTree && sPath[i].distance < minDist) {
minDist = sPath[i].distance;
indexMin = i;
}
}
return indexMin;
}
/**
* 更新sPath[]数组
*/
private void adjust_sPath() {
int column = 1;
while (column < nVerts) {
/** 如果已经在树中,跳至下一顶点 */
if (vertexList[column].isInTree) {
column++;
continue;
}
/** 获取到当前顶点的权值 */
int currentToFringe = adjMat[currentVert][column];
/** 获取路过路径的总权值 */
int startToFringe = startToCurrent + currentToFringe;
/** 获取sPath[]数组当前项的权值 */
int sPathDist = sPath[column].distance;
/** 如果startToFringe更小,则替换 */
if (startToFringe < sPathDist) {
sPath[column].parentVert = currentVert;
sPath[column].distance = startToFringe;
}
column++;
}
}
public void display() {
for (int i = 0; i < nVerts; i++) {
System.out.print(vertexList[i].label + "=");
if (sPath[i].distance == INFINITY)
System.out.print("inf");
else
System.out.print(sPath[i].distance);
char parent = vertexList[sPath[i].parentVert].label;
System.out.print("(" + parent + ") ");
}
System.out.println();
}
}
public static void main(String[] args) {
Graph graph = new Graph();
graph.addVertex('A');
graph.addVertex('C');
graph.addVertex('B');
graph.addVertex('D');
graph.addVertex('E');
graph.addEdge(0, 1, 50); //AB 50
graph.addEdge(0, 3, 80); //AD 80
graph.addEdge(1, 2, 60); //BC 60
graph.addEdge(1, 3, 90); //BD 90
graph.addEdge(2, 4, 40); //CE 40
graph.addEdge(3, 2, 20); //DC 20
graph.addEdge(3, 4, 70); //DE 70
graph.addEdge(4, 1, 50); //EB 50
System.out.println("Shortest paths");
graph.path();
System.out.println();
}
}