最短路径问题
可能在带权图中,最常用遇到的问题就是,寻找两个点间的最短路径问题。
这个问题的解决方法可以应用在现实生活中很多情况,从印刷电路板到项目的调度都适合,但是它
比前面见到过的问题更负责一些,所以首先还是来看一个真是世界的场景,它还是发生在前面的引入的那个虚拟的国家
铁路线。
这次我们来模拟的是铁路线而不是有线电路线了。然而,这个项目不像上一个那么浩大,这次并不是要修铁路
而是铁路已经建好了。这次只是想找到从一个到另一个城市的最低费用
旅客在两个城市间搭乘火车需要固定的费用。
图的边是有方向的,也就是有向带权的。我们要最关心的是路费的便宜了。最类问题叫做最短路径问题。这里说说的最短
并不一定只的是距离的最短,可能是费用的最少,时间的最少,效果的最好。、
最便宜的费用
任何两个城市间都有几条可能的路线,最短路径是这样的:
对一个给定的原点和终止点,走哪条线路费用最低,带有向带权图
正如前面提到的,铁路只有一个方向,所以火车在任何两个城市间只能朝一个方向进行。这相当于一个有向图,本来应该
描述一个更接近现实的情况,也就是乘客可以花同样的钱在两个城市间往返。那就相当于一个无向图了。然而最短路径的问题
在这两种情况下是类似的。所以为了体现多样性,我们来看这个问题在有向图中是怎样解决掉的
Dijkstra 算法
为了解决最短路径问题而提出来的方法叫做Dijkstra算法,Edsger Dijkstra在1959年解决了这个问题。
这个算法是的实现是基于图的连接矩阵的表示法中的。让人感到有些惊奇的是它不仅仅能够找到任意两点间的最短路径,
还可以找到某个指定点到其其他所有顶点的最短路径。
代理人和铁路路线
我们假设的要从a这个顶点开始,找到它到其他顶点的最低费用的路线。
我们开始我们的想法了:
在每个城市中,站长会告诉从该站到下一个站点的费用,也就是单程的费用。但是这个站长不知道,其他站的价格了。
这里需要用到一个记事本,本子为每个城市留一列位子,并且希望每一列的最后显示从源点到其他城市的最低费用。
第一个代理人:在A城市
最终,需要再每个城市放一个代理人,这个代理人的工作就是保持到其他城市的信息费用的信息的。你自己就是A的代理人
A城市站长所能告诉你的是到B城市的价格还有就是D城市的费用,把这些记录叜笔记本上,如果站长不知道,那么就记无限大。
意味着不能从A到达表中的每一列的列头所示的城市,或者至少目前为止不知道如何到达那里。 (这个信息有用的,不要着急)
规则
总是把本站代理人的弟弟派到下一个城市去,从源点到这个城市的最低费用。一起到B城市去,在那里成成为了代理人。当他达到那里的时候,
他将问站长到下一站 C 和 D的费用 其他是无线大
经过简单的计算以后,从A到B到C的费用110,所以修改了在笔记本上的条目,从A经过B到D的费用是140元
然而刚才知道了从A到D的费用是80元,由于值关心的是从A出发到目的地的最低费用,所以忽略这条最贵的路线了。笔记本上相应的条目也不做修改了
在某个城市有代理人以后,可以确定的是这个代理人走过的路费是费用最低的路线。为什么呢?考虑现在的情况,如果有另一个线路比从A到B的直接
连接更便宜,它需要通过其他的城市,但是从A厨房的另一条路线到D,它已经比到B的直接费用更加贵了。加上D到B的费用,使得这条费用更加贵了
因此可以确定的是,从现在开始,不需要改动A到B的最低费用了,不管找到什么城市,这个费用都是不会变的。在它的旁边标注一个*号,表示在这个
城市有一个代理人了,并且到它的最低费用是固定的。
/**
* path()方法真正的执行了最短路径算法,它使用DisPar类和Vertex类,这个类在mstw.java程序 * 原点总是在vertxList[] 数组下标为0的位子,path()方法的第一个任务是把这个顶点放入树中,算法 * 执行过程中,将会把其他顶点也逐一放入树中。把顶点放入树中的操作是设置标志,并把nTree变量增加1, * 这个变量记录了树中有多少个顶点。 * * 第二path()方法把邻接矩阵的对应行表达的距离的值复制到sPath[]中,实际总是先从第0行 * 复制,为了简单,假定源点的下标总为0,最开始,所有sPath[]数组中父节点字段为A,也就是源点。 * 现在进入算法的主要循环,等到所有的顶点都放入到树中,这个循环 就结束,这个循环中有是基本操作 * * 1. 选择sPath[]数组的最小距离 * 2. 把对应的顶点找出来(这个最小距离所在列的题头)放入树中,这个点变成当前的顶点,currentVer * 3.根据currentVert的变化,更新所有的sPath[]数组的内容。 *如果path()方法发现最小距离是无穷大,它就知道有些顶点从源点不能到达。为什么?因为不是所有的顶点都是在树中 *(while 循环没有结束),尚无方法可以到达那些树外的顶点;如果有,就不会足无穷大了的距离了 * * 返回前path()方法调用display显示 并且最后复位所有的顶点 * * * * * @return */ public int[] djisk() { // this.currentVetex = 0 ; // this.vertexList[0].setProxyer(true) ; //标记为设置了代理人的 // // for (int i=currentVetex+1; i<this.nVertex; i++) { // // if(this.adjMat[currentVetex][i] == this.BIGGER) { // // this.sPath[i] = this.BIGGER ; // continue ; // // } // // this.sPath[i] = this.adjMat[] // // // // } // // return null ; }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
迪杰斯特拉(Dijkstra)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
创建边 package endual; public class Edge { public int srcVert ; //一个顶点开始的序列好 public int destVert ; //一个顶点结束的序号 public int distance ;//从开始到介绍的长度 public Edge(int srcVert, int destVert, int distance) { super(); this.srcVert = srcVert; this.destVert = destVert; this.distance = distance; } }
创建顶点 package endual; public class Vertex { private char label; private boolean isInTree; public Vertex(char a) { this.label = a; this.isInTree = false; } public char getLabel() { return label; } public void setLabel(char label) { this.label = label; } public boolean isInTree() { return isInTree; } public void setInTree(boolean isInTree) { this.isInTree = isInTree; } }
创建一个辅助的类
package endual; public class DistPar { private int distance; private int parentVert; public DistPar(int distance, int parentVert) { super(); this.distance = distance; this.parentVert = parentVert; } public int getDistance() { return distance; } public void setDistance(int distance) { this.distance = distance; } public int getParentVert() { return parentVert; } public void setParentVert(int parentVert) { this.parentVert = parentVert; } }
创建一个图
其中path就是最短路径算法了
package endual; public class Graph { private final int MAX_SIZE = 20; private final int INF = 1000000; private Vertex[] vertexList; private int adjMat[][]; private int nVerts; private int nTree; private DistPar sPath[]; private int currrentSize; private int startTOCurrent; private int currentVertex; public Graph() { super(); this.vertexList = new Vertex[this.MAX_SIZE]; this.adjMat = new int[this.MAX_SIZE][this.MAX_SIZE]; initialAdjMat(); this.nVerts = 0; this.nTree = 0; this.sPath = new DistPar[this.MAX_SIZE]; this.currrentSize = 0; this.startTOCurrent = 0; } private void initialAdjMat() { for (int i = 0; i < this.MAX_SIZE; i++) { for (int j = 0; j < this.MAX_SIZE; j++) { this.adjMat[i][j] = this.INF; } } } // 插入顶点 public void insert(char a) { Vertex v = new Vertex(a); this.vertexList[this.nVerts] = v; this.nVerts++; } // 插入边怎么没有边啊啊 public void addEdge(int start, int end, int weight) { this.adjMat[start][end] = weight; } public void path() { int startTree = 0; this.vertexList[startTree].setInTree(true); // 已经放入到树中了 this.nTree++; // 转换虫adjMat的列的长度到数组中去sPath for (int j = 0; j < this.nVerts; j++) { int tempDist = this.adjMat[startTree][j]; DistPar tempDistPar = new DistPar(startTree, tempDist); sPath[j] = tempDistPar; } while (this.nTree < this.nVerts) { // 从sPath()中选择出边的长度最短的,这个getMin的是这个边的标号,最先的indexMin的标号 int indexMin = getMin(); // int minDist = this.sPath[indexMin].getDistance(); // 得到这个长度 if (minDist == this.INF) { System.out.printf("None Edges"); break; } else { currentVertex = indexMin; // 当前的顶点的在数组中的标号 this.startTOCurrent = sPath[indexMin].getDistance(); // 当前顶点连接各个边的最短的长度 // 最小的长度从开始的顶点开始的,转换到当前的顶点,然后 } this.vertexList[this.currentVertex].setInTree(true); // 设置到树中了 this.nTree++; adjust_sPath(); // 更新spathp[]数组 } //end while displayPaths() ; this.nTree = 0 ; for (int j=0; j < this.nVerts; j++) { this.vertexList[j].setInTree(false) ; } } //显示所有的最短路径 private void displayPaths() { for (int i=0; i < this.nVerts; i++) { System.out.println(this.vertexList[i].getLabel()) ; if (this.sPath[i].getDistance() == this.INF) { System.out.println("Inf") ; }else { System.out.println(this.sPath[i].getDistance()) ; } char parent = this.vertexList[this.sPath[i].getDistance()].getLabel() ; System.out.print("{" + parent + "}") ; } System.out.println(" ") ; } private void adjust_sPath() { int column = 1 ; while (column < this.nVerts) { if (this.vertexList[column].isInTree()) { column++ ; continue ; } // end if int currentToFringe = this.adjMat[this.currentVertex][column] ; int startToFrige = this.startTOCurrent + currentToFringe ; int sPathDist = this.sPath[column].getDistance() ; if (startToFrige < sPathDist) { this.sPath[column].setParentVert(currentVertex) ; this.sPath[column].setDistance(startToFrige) ; } column++ ; } //end while } private int getMin() { int minDist = this.INF; int indexMin = 0; for (int j = 1; j < this.nVerts; j++) { if (!this.vertexList[j].isInTree() && this.sPath[j].getDistance() < minDist) { minDist = this.sPath[j].getDistance(); indexMin = j; } } return indexMin; } }