标签:
上一节我们已经看到了图的边可以有方向,这一节里,我们将探讨边的另一个特性:权值。例如,如果带权图的顶点代表城市,边的权可能代表城市之间的距离,或者城市之间的路费,或者之间的车流量等等。
带权图归根究底还是图,上一节那些图的基本操作,例如广度优先搜索和深度优先搜索等都是一样的,在这一节里,我们主要来探讨一下带权图的最小生成树最短路径问题。
最小生成树问题
首先探讨下最小生成树问题,它与上一节所提到的最小生成树不同。上一节的最小生成树是个特例,即所有边的权值都一样。那么算法如何设计呢?建议用优先级队列来实现这个反复选择最小的路径,而不是链表或数组,这是解决最小生成树的有效方式。在正式的程序中,优先级队列可能基于堆来实现(关于堆,可参见第8节内容),这会加快在较大的优先级队列中的操作。但是在本例中,我们使用数组实现优先级队列,仅仅为了说明算法。算法要点如下:
从一个顶点开始,把它放入树的集合中,然后重复做下面的事情:
1. 找到从最新的顶点到其他顶点的所有边,这些顶点不能在树的集合中,把这些边放入优先级队列中。
2. 找出权值最小的边,把它和它所达到的顶点放入树的集合中。
重复这些步骤,直到所有的顶点都在树的集合中,这时工作完成。
下面先看一下最小生成树的代码,然后再解释一些细节上的问题:
-
-
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;
-
}
-
}
-
-
-
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-1];
-
}
-
-
public Edge peekN(int n) {
-
return queArray[n];
-
}
-
-
public int size() {
-
return size;
-
}
-
-
public boolean isEmpty() {
-
return (size == 0);
-
}
-
-
public int find(int findDex) {
-
for(int j = 0; j < size; j++) {
-
if(queArray[j].destVert == findDex)
-
return j;
-
}
-
return -1;
-
}
-
}
-
-
-
public class WeightedGraph {
-
private final int MAX_VERTS = 20;
-
private final int INFINITY = 100000;
-
private Vertex[] vertexArray;
-
private int adjMat[][];
-
private int nVerts;
-
private int currentVert;
-
private PriorityQ thePQ;
-
private int nTree;
-
-
public WeightedGraph() {
-
vertexArray = new Vertex[MAX_VERTS];
-
adjMat = new int[MAX_VERTS][MAX_VERTS];
-
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) {
-
vertexArray[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.print(vertexArray[v].label);
-
}
-
-
-
-
-
public void MinSpanningTree() {
-
currentVert = 0;
-
while(nTree < nVerts-1) {
-
-
-
vertexArray[currentVert].isInTree = true;
-
nTree++;
-
-
-
for(int i = 0; i < nVerts; i++) {
-
if(i == currentVert)
-
continue;
-
if(vertexArray[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(vertexArray[sourceVert].label);
-
System.out.print(vertexArray[currentVert].label);
-
System.out.print(" ");
-
}
-
}
-
-
private 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);
-
}
-
}
-
}
算法在while循环中执行,循环结束条件是所有顶点都已在树中。
1. 当前顶点放在树中。
2. 连接这个顶点的边放到优先级队列中(如果合适)。
3. 从优先级队列中删除权值最小的边,这条边的目的顶点变成当前顶点。
再看看这些步骤的细节:1中,通过标记currentVert所指顶点的isInTree字段来表示该顶点放入树中,2中,连接这个顶点的边插入优先级队列。通过在邻接矩阵中扫描行号是currentVert的行寻找需要的边。只要下面任意一个条件为真,这条边就不能放入队列中:
1.源点和终点相同;
2. 终点在树中;
3. 源点和终点之间没有边(邻接矩阵中对应的值等于无穷大)。
如果没有一个条件为真,调用putInPQ()方法把这条边放入队列中。实际上并不一定会将这条边放入队列中,还得进行判断。步骤3中,将最小权值的边从优先级队列中删除。把这条边和该边的重点加入树,并显示源点和终点。
最后,所有顶点的isInTree变量被重置,即从树中删除。在该程序这样做,是因为根据这些数据只能创建一棵树。然后在完成一项工作后,最好把数据恢复到原始的形态。
接下来探讨下最短路径问题:
最短路径问题
在带权图中最常遇到的问题就是寻找两点间的最短路径问题,这个问题的解决方法可应用于现实生活中的很多地方。但是它比前面遇到的问题更加复杂一些。为了解决最短路径问题而提出的方法叫做Djikstra算法。这个算法的实现基于图的邻接矩阵表示法,它不仅能够找到任意两点间的最短路径,还可以找到某个指定点到其他所有顶点的最短路径。
为了实现这个算法,首先得建一个辅助类DistPar类,这个类中封装了到初始顶点的距离以及父顶点的信息。
-
-
class DistPar {
-
public int distance;
-
public int parentVert;
-
-
public DistPar(int pv, int d) {
-
distance = d;
-
parentVert = pv;
-
}
-
}
另外还得有个数组,这是最短路径算法中的一个关键数据结构,它保持了从源点到其他顶点(终点)的最短路径。在算法的执行过程中这个距离是变化的,知道最后,它存储了从源点开始的真正最短距离。这个数组定义为WeightedGraph的一个私有成员变量:
-
private DistPar[] sPath;
-
private int startToCurrent;
另外需要在构造函数中将其初始化:sPath =new DistPar[MAX_VERTS];
下面详细分析最短路径算法中涉及的几个方法,这都是WeightedGraph类中的方法,在这里我抽出来分析的,最后会附上完整的WeightedGraph类代码
由于图的表示法有两种,邻接矩阵和邻接表。是的图的算法效率问题变得相对复杂。如果使用邻接矩阵,前面讨论的算法大多需要O(V2)的时间级,V表示顶点数量。因为这些算法几乎都检查了一遍所有的顶点,具体方法是在邻接矩阵中扫描每一行,一次查看每一条边。换句话说,邻接矩阵的V2个单元都被扫描过。
对于大规模的矩阵,O(V2)的时间基本是非常好的性能。如果图是密集的,那就没什么提高性能的余地了(密集意味着图有很多边,而它的邻接矩阵的许多或大部分单元被占)。然而,许多图是稀疏的,其实并没有一个确定数量的定义说明多少条边的图才是密集的或稀疏的,但如果在一个大图中每个顶点只有很少几条边相连,那么这个图通常被认为是稀疏的。
在稀疏图中,使用邻接表的表示方法代替邻接矩阵,可以改善运行时间,因为不必浪费时间来检索邻接矩阵中没有边的单元。对于无权图,邻接表的深度优先搜索需要O(V+E)的时间级,V是顶点数量,E是边数。对于带权图,最小生成树算法和最短路径算法都需要O(E+V)logV)的时间级,在大型的稀疏图中,与邻接矩阵方法的时间级O(V2)相比,这样的时间级可使性能大幅提升,但是算法会复杂一些。
完整代码
下面附上有权图的完整代码和测试代码
-
package graph;
-
-
-
-
-
-
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;
-
}
-
}
-
-
-
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-1];
-
}
-
-
public Edge peekN(int n) {
-
return queArray[n];
-
}
-
-
public int size() {
-
return size;
-
}
-
-
public boolean isEmpty() {
-
return (size == 0);
-
}
-
-
public int find(int findDex) {
-
for(int j = 0; j < size; j++) {
-
if(queArray[j].destVert == findDex)
-
return j;
-
}
-
return -1;
-
}
-
}
-
-
-
class DistPar {
-
public int distance;
-
public int parentVert;
-
-
public DistPar(int pv, int d) {
-
distance = d;
-
parentVert = pv;
-
}
-
}
-
-
-
public class WeightedGraph {
-
private final int MAX_VERTS = 20;
-
private final int INFINITY = 100000;
-
private Vertex[] vertexArray;
-
private int adjMat[][];
-
private int nVerts;
-
private int currentVert;
-
private PriorityQ thePQ;
-
private int nTree;
-
-
private DistPar[] sPath;
-
private int startToCurrent;
-
-
public WeightedGraph() {
-
vertexArray = new Vertex[MAX_VERTS];
-
adjMat = new int[MAX_VERTS][MAX_VERTS];
-
for(int i = 0; i < MAX_VERTS; i++) {
-
for(int j = 0; j < MAX_VERTS; j++) {
-
adjMat[i][j] = INFINITY;
-
}
-
}
-
thePQ = new PriorityQ();
-
sPath = new DistPar[MAX_VERTS];
-
}
-
-
public void addVertex(char lab) {
-
vertexArray[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.print(vertexArray[v].label);
-
}
-
-
-
public void MinSpanningTree() {
-
currentVert = 0;
-
while(nTree < nVerts-1) {
-
vertexArray[currentVert].isInTree = true;
-
nTree++;
-
-
-
for(int i = 0; i < nVerts; i++) {
-
if(i == currentVert)
-
continue;
-
if(vertexArray[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(vertexArray[sourceVert].label);
-
System.out.print(vertexArray[currentVert].label);
-
System.out.print(" ");
-
}
-
}
-
-
private 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 void path() {
-
-
-
-
-
-
int startTree = 0;
-
vertexArray[startTree].isInTree = true;
-
nTree = 1;
-
-
-
-
-
-
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;
-
startToCurrent = sPath[indexMin].distance;
-
}
-
vertexArray[currentVert].isInTree = true;
-
nTree++;
-
-
adjust_sPath();
-
}
-
displayPaths();
-
-
nTree = 0;
-
for(int i = 0; i < nVerts; i++) {
-
vertexArray[i].isInTree = false;
-
}
-
}
-
-
-
private int getMin() {
-
int minDist = INFINITY;
-
int indexMin = 0;
-
for(int i = 0; i < nVerts; i++) {
-
if(!vertexArray[i].isInTree && sPath[i].distance < minDist) {
-
minDist = sPath[i].distance;
-
indexMin = i;
-
}
-
}
-
return indexMin;
-
}
-
-
-
-
-
private void adjust_sPath() {
-
int column = 1;
-
while(column < nVerts) {
-
if(vertexArray[column].isInTree) {
-
column++;
-
continue;
-
}
-
int currentToFringe = adjMat[currentVert][column];
-
int startToFringe = startToCurrent + currentToFringe;
-
int sPathDist = sPath[column].distance;
-
-
if(startToFringe < sPathDist) {
-
sPath[column].parentVert = currentVert;
-
sPath[column].distance = startToFringe;
-
}
-
column++;
-
}
-
}
-
-
private void displayPaths() {
-
for(int i = 0; i < nVerts; i++) {
-
System.out.print(vertexArray[i].label + "=");
-
if(sPath[i].distance == INFINITY)
-
System.out.print("infinity");
-
else
-
System.out.print(sPath[i].distance);
-
char parent = vertexArray[sPath[i].parentVert].label;
-
System.out.print("(" + parent + ") ");
-
}
-
System.out.println("");
-
}
-
-
}
下面是测试用例:
-
package test;
-
import graph.WeightedGraph;
-
-
public class Test {
-
public static void main(String[] args){
-
WeightedGraph arr = new WeightedGraph();
-
arr.addVertex(‘A‘);
-
arr.addVertex(‘B‘);
-
arr.addVertex(‘C‘);
-
arr.addVertex(‘D‘);
-
arr.addVertex(‘E‘);
-
-
arr.addEdge(0, 1, 50);
-
arr.addEdge(0, 3, 80);
-
arr.addEdge(1, 2, 60);
-
arr.addEdge(1, 3, 90);
-
arr.addEdge(2, 4, 40);
-
arr.addEdge(3, 2, 20);
-
arr.addEdge(3, 4, 70);
-
arr.addEdge(4, 1, 50);
-
-
arr.MinSpanningTree();
-
System.out.println(" ");
-
arr.path();
-
}
-
}
输出结果为:
-
AB BE EC CD
-
A=infinity(A) B=50(A) C=100(D) D=80(A) E=100(B)
算法10 之带权图
标签:
原文地址:http://blog.csdn.net/luo451591667/article/details/51397105