数据结构和算法10 之带权图

简介:
    上一节我们已经看到了图的边可以有方向,这一节里,我们将探讨边的另一个特性:权值。例如,如果带权图的顶点代表城市,边的权可能代表城市之间的距离,或者城市之间的路费,或者之间的车流量等等。

    带权图归根究底还是图,上一节那些图的基本操作,例如广度优先搜索和深度优先搜索等都是一样的,在这一节里,我们主要来探讨一下带权图的最小生成树最短路径问题。

最小生成树问题

    首先探讨下最小生成树问题,它与上一节所提到的最小生成树不同。上一节的最小生成树是个特例,即所有边的权值都一样。那么算法如何设计呢?建议用优先级队列来实现这个反复选择最小的路径,而不是链表或数组,这是解决最小生成树的有效方式。在正式的程序中,优先级队列可能基于堆来实现(关于堆,可参见第8节内容),这会加快在较大的优先级队列中的操作。但是在本例中,我们使用数组实现优先级队列,仅仅为了说明算法。算法要点如下:

    从一个顶点开始,把它放入树的集合中,然后重复做下面的事情:

    1. 找到从最新的顶点到其他顶点的所有边,这些顶点不能在树的集合中,把这些边放入优先级队列中。

    2. 找出权值最小的边,把它和它所达到的顶点放入树的集合中。

    重复这些步骤,直到所有的顶点都在树的集合中,这时工作完成。

    下面先看一下最小生成树的代码,然后再解释一些细节上的问题:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //边界路径类,主要记录了边的始末顶点,以及边的权值  
  2. class Edge {  
  3.     public int srcVert; //index of vertex starting edge  
  4.     public int destVert; //index of vertex ending edge  
  5.     public int distance; //distance from src to dest  
  6.       
  7.     public Edge(int sv, int dv, int d) {  
  8.         srcVert = sv;  
  9.         destVert = dv;  
  10.         distance = d;  
  11.     }  
  12. }  
  13.   
  14. //自定义优先队列,用来存储边  
  15. class PriorityQ {  
  16.     private final int SIZE = 20;  
  17.     private Edge[] queArray; //存储边界的数组  
  18.     private int size;  
  19.       
  20.     public PriorityQ() {  
  21.         queArray = new Edge[SIZE];  
  22.         size = 0;  
  23.     }  
  24.       
  25.     public void insert(Edge item) { //有序的插入边界  
  26.         int j;  
  27.         for(j = 0; j < size; j++) { //找到插入的位置,从0到size-1,逐渐减小  
  28.             if(item.distance >= queArray[j].distance)   
  29.                 break;  
  30.         }  
  31.         //比item.distance小的往后挪一位,给腾出个空间  
  32.         for(int k = size-1; k >= j; k--) {  
  33.             queArray[k+1] = queArray[k];  
  34.         }  
  35.         queArray[j] = item; //插入item  
  36.         size++;  
  37.     }  
  38.       
  39.     public Edge removeMin() { //删除最小的边界并返回  
  40.         return queArray[--size];  
  41.     }  
  42.       
  43.     public void removeN(int n) { //删除n位置的边界  
  44.         for(int j = n; j < size-1; j++) {  
  45.             queArray[j] = queArray[j+1];  
  46.         }  
  47.         size--;  
  48.     }  
  49.       
  50.     public Edge peekMin() { //返回最小边界,不删除  
  51.         return queArray[size-1];  
  52.     }  
  53.       
  54.     public Edge peekN(int n) { //返回n位置的边界  
  55.         return queArray[n];  
  56.     }  
  57.       
  58.     public int size() {  
  59.         return size;  
  60.     }  
  61.       
  62.     public boolean isEmpty() {  
  63.         return (size == 0);  
  64.     }  
  65.       
  66.     public int find(int findDex) { //寻找特定disVert的边界索引  
  67.         for(int j = 0; j < size; j++) {  
  68.             if(queArray[j].destVert == findDex)  
  69.                 return j;  
  70.         }  
  71.         return -1;  
  72.     }  
  73. }  
  74.   
  75. //带权图类  
  76. public class WeightedGraph {  
  77.     private final int MAX_VERTS = 20//最大顶点数  
  78.     private final int INFINITY = 100000//最远距离...表示无法达到  
  79.     private Vertex[] vertexArray; //存储顶点的数组  
  80.     private int adjMat[][]; //存储顶点之间的边界  
  81.     private int nVerts; //顶点数量  
  82.     private int currentVert; //当前顶点索引  
  83.     private PriorityQ thePQ; //存储边的优先级队列  
  84.     private int nTree; //最小生成树中的顶点数量  
  85.       
  86.     public WeightedGraph() {  
  87.         vertexArray = new Vertex[MAX_VERTS];  
  88.         adjMat = new int[MAX_VERTS][MAX_VERTS];  
  89.         for(int i = 0; i < MAX_VERTS; i++) {  
  90.             for(int j = 0; j < MAX_VERTS; j++) {  
  91.                 adjMat[i][j] = INFINITY; //初始化所有边界无穷远  
  92.             }  
  93.         }  
  94.         thePQ = new PriorityQ();  
  95.     }  
  96.       
  97.     public void addVertex(char lab) { //添加顶点  
  98.         vertexArray[nVerts++] = new Vertex(lab);  
  99.     }  
  100.       
  101.     public void addEdge(int start, int end, int weight) {//添加带权边  
  102.         adjMat[start][end] = weight;  
  103.         adjMat[end][start] = weight;  
  104.     }  
  105.       
  106.     public void displayVertex(int v) {  
  107.         System.out.print(vertexArray[v].label);  
  108.     }  
  109.       
  110.     /* 
  111.      * 带权图的最小生成树,要选择一条最优的路径 
  112.      */  
  113.     public void MinSpanningTree() {  
  114.         currentVert = 0//从0开始  
  115.         while(nTree < nVerts-1) { //当不是所有节点都在最小生成树中时  
  116.             //isInTree是上一节Vertex类中新添加的成员变量 private boolean isInTree;  
  117.                //表示有没有加入到树中,初始化为false  
  118. vertexArray[currentVert].isInTree = true//将当前顶点加到树中  
  119.             nTree++;  
  120.               
  121.             //往PQ中插入与当前顶点相邻的一些边界  
  122.             for(int i = 0; i < nVerts; i++) {  
  123.                 if(i == currentVert) //如果是本顶点,跳出  
  124.                     continue;  
  125.                 if(vertexArray[i].isInTree) //如果顶点i已经在树中,跳出  
  126.                     continue;  
  127.                 int distance = adjMat[currentVert][i]; //计算当前顶点到i顶点的距离  
  128.                 if(distance == INFINITY)   
  129.                     continue//如果当前顶点与i顶点无穷远,跳出  
  130.                 putInPQ(i, distance); //将i节点加入PQ中  
  131.             }  
  132.               
  133.             if(thePQ.size() == 0) { //如果PQ为空,表示图不连接  
  134.                 System.out.println("Graph not connected!");  
  135.                 return;  
  136.             }  
  137.               
  138.             Edge theEdge = thePQ.removeMin();  
  139.             int sourceVert = theEdge.srcVert;  
  140.             currentVert = theEdge.destVert;  
  141.               
  142.             System.out.print(vertexArray[sourceVert].label);  
  143.             System.out.print(vertexArray[currentVert].label);  
  144.             System.out.print(" ");  
  145.         }  
  146.     }  
  147.   
  148.     private void putInPQ(int newVert, int newDist) {  
  149.         int queueIndex = thePQ.find(newVert);//判断PQ中是否已经有到相同目的顶点的边界  
  150.         if(queueIndex != -1) { //如果有则与当前顶点到目的顶点的距离作比较,保留短的那个  
  151.             Edge tempEdge = thePQ.peekN(queueIndex);//get edge  
  152.             int oldDist = tempEdge.distance;  
  153.             if(oldDist > newDist) { //如果新的边界更短  
  154.                 thePQ.removeN(queueIndex); //删除旧边界  
  155.                 Edge theEdge = new Edge(currentVert, newVert, newDist);  
  156.                 thePQ.insert(theEdge);  
  157.             }  
  158.         }  
  159.         else { //如果PQ中没有到相同目的顶点的边界  
  160.             Edge theEdge = new Edge(currentVert, newVert, newDist);  
  161.             thePQ.insert(theEdge);//直接添加到PQ  
  162.         }  
  163.     }  
  164. }  

    算法在while循环中执行,循环结束条件是所有顶点都已在树中。

    1. 当前顶点放在树中。

    2. 连接这个顶点的边放到优先级队列中(如果合适)。

    3. 从优先级队列中删除权值最小的边,这条边的目的顶点变成当前顶点。

    再看看这些步骤的细节:1中,通过标记currentVert所指顶点的isInTree字段来表示该顶点放入树中,2中,连接这个顶点的边插入优先级队列。通过在邻接矩阵中扫描行号是currentVert的行寻找需要的边。只要下面任意一个条件为真,这条边就不能放入队列中:

    1.源点和终点相同;

    2. 终点在树中;

    3. 源点和终点之间没有边(邻接矩阵中对应的值等于无穷大)。

    如果没有一个条件为真,调用putInPQ()方法把这条边放入队列中。实际上并不一定会将这条边放入队列中,还得进行判断。步骤3中,将最小权值的边从优先级队列中删除。把这条边和该边的重点加入树,并显示源点和终点。

最后,所有顶点的isInTree变量被重置,即从树中删除。在该程序这样做,是因为根据这些数据只能创建一棵树。然后在完成一项工作后,最好把数据恢复到原始的形态。

    接下来探讨下最短路径问题:

最短路径问题

    在带权图中最常遇到的问题就是寻找两点间的最短路径问题,这个问题的解决方法可应用于现实生活中的很多地方。但是它比前面遇到的问题更加复杂一些。为了解决最短路径问题而提出的方法叫做Djikstra算法。这个算法的实现基于图的邻接矩阵表示法,它不仅能够找到任意两点间的最短路径,还可以找到某个指定点到其他所有顶点的最短路径。

为了实现这个算法,首先得建一个辅助类DistPar类,这个类中封装了到初始顶点的距离以及父顶点的信息。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //DistPar类记录了当前顶点到起始顶点点的距离和当前顶点的父顶点  
  2. class DistPar {  
  3.     public int distance; //distance from start to this vertex  
  4.     public int parentVert; //current parent of this vertex  
  5.       
  6.     public DistPar(int pv, int d) {  
  7.         distance = d;  
  8.         parentVert = pv;  
  9.     }  
  10. }  
    另外还得有个数组,这是最短路径算法中的一个关键数据结构,它保持了从源点到其他顶点(终点)的最短路径。在算法的执行过程中这个距离是变化的,知道最后,它存储了从源点开始的真正最短距离。这个数组定义为WeightedGraph的一个私有成员变量:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private DistPar[] sPath; //存储最短路径数据,存储的是上面的DistPar对象  
  2. private int startToCurrent; //到当前顶点的距离  

    另外需要在构造函数中将其初始化:sPath =new DistPar[MAX_VERTS];

    下面详细分析最短路径算法中涉及的几个方法,这都是WeightedGraph类中的方法,在这里我抽出来分析的,最后会附上完整的WeightedGraph类代码

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /************************** 最短路径问题 ****************************/  
  2. /** 
  3.  * path()方法执行真正的最短路径算法。 
  4.  */  
  5. public void path() { //寻找所有最短路径  
  6.     /* 
  7.      * 源点总在vertexArray[]数组下标为0的位置,path()方法的第一个任务就是把这个顶点放入树中。 
  8.      * 算法执行过程中,将会把其他顶点也逐一放入树中。把顶点放入树中的操作是设置一下标志位即可。 
  9.      * 并把nTree变量增1,这个变量记录了树中有多少个顶点。 
  10.      */  
  11.     int startTree = 0//从vertex 0开始  
  12.     vertexArray[startTree].isInTree = true;  
  13.     nTree = 1;  
  14.       
  15.     /* 
  16.      * path()方法把邻接矩阵的对应行表达的距离复制到sPath[]中,实际总是先从第0行复制 
  17.      * 为了简单,假定源点的下标总为0。最开始,所有sPath[]数组中的父节点字段为A,即源点。 
  18.      */  
  19.     for(int i = 0; i < nVerts; i++) {  
  20.         int tempDist = adjMat[startTree][i];  
  21.         //sPath中保存的都是到初始顶点的距离,所以父顶点默认都是初始顶点,后面程序中会将其修改  
  22.         sPath[i] = new DistPar(startTree, tempDist);  
  23.     }  
  24.       
  25.     /* 
  26.      * 现在进入主循环,等到所有的顶点都放入树中,这个循环就结束,这个循环有三个基本动作: 
  27.      * 1. 选择sPath[]数组中的最小距离 
  28.      * 2. 把对应的顶点(这个最小距离所在列的题头)放入树中,这个顶点变成“当前顶点”currentVert 
  29.      * 3. 根据currentVert的变化,更新所有的sPath[]数组内容 
  30.      */  
  31.     while(nTree < nVerts) {  
  32.         //1. 选择sPath[]数组中的最小距离  
  33.         int indexMin = getMin(); //获得sPath中的最小路径值索引  
  34.         int minDist = sPath[indexMin].distance; //获得最小路径  
  35.           
  36.         if(minDist == INFINITY) {  
  37.             System.out.println("There are unreachable vertices");  
  38.             break;  
  39.         }  
  40.         //2. 把对应的顶点(这个最小距离所在列的题头)放入树中,这个顶点变成“当前顶点”currentVert  
  41.         else { //reset currentVert  
  42.             currentVert = indexMin;  
  43.             startToCurrent = sPath[indexMin].distance;  
  44.         }  
  45.         vertexArray[currentVert].isInTree = true;  
  46.         nTree++;  
  47.         //3. 根据currentVert的变化,更新所有的sPath[]数组内容  
  48.         adjust_sPath();  
  49.     }  
  50.     displayPaths();  
  51.       
  52.     nTree = 0;  
  53.     for(int i = 0; i < nVerts; i++) {  
  54.         vertexArray[i].isInTree = false;  
  55.     }  
  56. }  
  57.   
  58. //获取sPath中最小路径的索引  
  59. private int getMin() {  
  60.     int minDist = INFINITY;  
  61.     int indexMin = 0;  
  62.     for(int i = 0; i < nVerts; i++) {  
  63.         if(!vertexArray[i].isInTree && sPath[i].distance < minDist) {  
  64.             minDist = sPath[i].distance;  
  65.             indexMin = i;  
  66.         }  
  67.     }  
  68.     return indexMin;  
  69. }  
  70.   
  71. /*调整sPath中存储的对象的值,即顶点到初始顶点的距离,和顶点的父顶点 
  72.  * 这是Dijkstra算法的核心 
  73.  */  
  74. private void adjust_sPath() {  
  75.     int column = 1;  
  76.     while(column < nVerts) {  
  77.         if(vertexArray[column].isInTree) {  
  78.             column++;  
  79.             continue;  
  80.         }  
  81.         int currentToFringe = adjMat[currentVert][column]; //获得当前顶点到其他顶点的距离,其他顶点不满足isInTree  
  82.         int startToFringe = startToCurrent + currentToFringe; //计算其他顶点到初始顶点的距离=当前顶点到初始顶点距离+当前顶点到其他顶点的距离  
  83.         int sPathDist = sPath[column].distance; //获得column处顶点到起始顶点的距离,如果不与初始顶点相邻,默认值都是无穷大  
  84.           
  85.         if(startToFringe < sPathDist) {  
  86.             sPath[column].parentVert = currentVert; //修改其父顶点  
  87.             sPath[column].distance = startToFringe; //以及到初始顶点的距离  
  88.         }  
  89.         column++;  
  90.     }  
  91. }  
  92. //显示路径  
  93. private void displayPaths() {  
  94.     for(int i = 0; i < nVerts; i++) {  
  95.         System.out.print(vertexArray[i].label + "=");  
  96.         if(sPath[i].distance == INFINITY)  
  97.             System.out.print("infinity");  
  98.         else  
  99.             System.out.print(sPath[i].distance);  
  100.         char parent = vertexArray[sPath[i].parentVert].label;  
  101.         System.out.print("(" + parent + ") ");  
  102.     }  
  103.     System.out.println("");  
  104. }  

    由于图的表示法有两种,邻接矩阵和邻接表。是的图的算法效率问题变得相对复杂。如果使用邻接矩阵,前面讨论的算法大多需要O(V2)的时间级,V表示顶点数量。因为这些算法几乎都检查了一遍所有的顶点,具体方法是在邻接矩阵中扫描每一行,一次查看每一条边。换句话说,邻接矩阵的V2个单元都被扫描过。

    对于大规模的矩阵,O(V2)的时间基本是非常好的性能。如果图是密集的,那就没什么提高性能的余地了(密集意味着图有很多边,而它的邻接矩阵的许多或大部分单元被占)。然而,许多图是稀疏的,其实并没有一个确定数量的定义说明多少条边的图才是密集的或稀疏的,但如果在一个大图中每个顶点只有很少几条边相连,那么这个图通常被认为是稀疏的。

    在稀疏图中,使用邻接表的表示方法代替邻接矩阵,可以改善运行时间,因为不必浪费时间来检索邻接矩阵中没有边的单元。对于无权图,邻接表的深度优先搜索需要O(V+E)的时间级,V是顶点数量,E是边数。对于带权图,最小生成树算法和最短路径算法都需要O(E+V)logV)的时间级,在大型的稀疏图中,与邻接矩阵方法的时间级O(V2)相比,这样的时间级可使性能大幅提升,但是算法会复杂一些。

完整代码

    下面附上有权图的完整代码和测试代码
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package graph;  
  2. /** 
  3.  * @desciption 带权图的完整代码 
  4.  * @author eson_15 
  5.  */  
  6. //边界路径类  
  7. class Edge {  
  8.     public int srcVert; //index of vertex starting edge  
  9.     public int destVert; //index of vertex ending edge  
  10.     public int distance; //distance from src to dest  
  11.       
  12.     public Edge(int sv, int dv, int d) {  
  13.         srcVert = sv;  
  14.         destVert = dv;  
  15.         distance = d;  
  16.     }  
  17. }  
  18.   
  19. //优先队列  
  20. class PriorityQ {  
  21.     private final int SIZE = 20;  
  22.     private Edge[] queArray; //存储边界的数组  
  23.     private int size;  
  24.       
  25.     public PriorityQ() {  
  26.         queArray = new Edge[SIZE];  
  27.         size = 0;  
  28.     }  
  29.       
  30.     public void insert(Edge item) { //有序的插入边界  
  31.         int j;  
  32.         for(j = 0; j < size; j++) { //找到插入的位置,从0到size-1,逐渐减小  
  33.             if(item.distance >= queArray[j].distance)   
  34.                 break;  
  35.         }  
  36.         //比item.distance小的往后挪一位,给腾出个空间  
  37.         for(int k = size-1; k >= j; k--) {  
  38.             queArray[k+1] = queArray[k];  
  39.         }  
  40.         queArray[j] = item; //插入item  
  41.         size++;  
  42.     }  
  43.       
  44.     public Edge removeMin() { //删除最小的边界并返回  
  45.         return queArray[--size];  
  46.     }  
  47.       
  48.     public void removeN(int n) { //删除n位置的边界  
  49.         for(int j = n; j < size-1; j++) {  
  50.             queArray[j] = queArray[j+1];  
  51.         }  
  52.         size--;  
  53.     }  
  54.       
  55.     public Edge peekMin() { //返回最小边界,不删除  
  56.         return queArray[size-1];  
  57.     }  
  58.       
  59.     public Edge peekN(int n) { //返回n位置的边界  
  60.         return queArray[n];  
  61.     }  
  62.       
  63.     public int size() {  
  64.         return size;  
  65.     }  
  66.       
  67.     public boolean isEmpty() {  
  68.         return (size == 0);  
  69.     }  
  70.       
  71.     public int find(int findDex) { //寻找特定disVert的边界索引  
  72.         for(int j = 0; j < size; j++) {  
  73.             if(queArray[j].destVert == findDex)  
  74.                 return j;  
  75.         }  
  76.         return -1;  
  77.     }  
  78. }  
  79.   
  80. //DistPar类记录了当前顶点到起始顶点点的距离和当前顶点的父顶点  
  81. class DistPar {  
  82.     public int distance; //distance from start to this vertex  
  83.     public int parentVert; //current parent of this vertex  
  84.       
  85.     public DistPar(int pv, int d) {  
  86.         distance = d;  
  87.         parentVert = pv;  
  88.     }  
  89. }  
  90.   
  91. //带权图类  
  92. public class WeightedGraph {  
  93.     private final int MAX_VERTS = 20//最大顶点数  
  94.     private final int INFINITY = 100000//最远距离...表示无法达到  
  95.     private Vertex[] vertexArray; //存储顶点的数组  
  96.     private int adjMat[][]; //存储顶点之间的边界  
  97.     private int nVerts; //顶点数量  
  98.     private int currentVert; //当前顶点索引  
  99.     private PriorityQ thePQ; //  
  100.     private int nTree; //最小生成树中的顶点数量  
  101.       
  102.     private DistPar[] sPath; //存储最短路径数据  
  103.     private int startToCurrent; //到当前顶点的距离  
  104.       
  105.     public WeightedGraph() {  
  106.         vertexArray = new Vertex[MAX_VERTS];  
  107.         adjMat = new int[MAX_VERTS][MAX_VERTS];  
  108.         for(int i = 0; i < MAX_VERTS; i++) {  
  109.             for(int j = 0; j < MAX_VERTS; j++) {  
  110.                 adjMat[i][j] = INFINITY; //初始化所有边界无穷远  
  111.             }  
  112.         }  
  113.         thePQ = new PriorityQ();  
  114.         sPath = new DistPar[MAX_VERTS];  
  115.     }  
  116.       
  117.     public void addVertex(char lab) { //添加顶点  
  118.         vertexArray[nVerts++] = new Vertex(lab);  
  119.     }  
  120.       
  121.     public void addEdge(int start, int end, int weight) {//添加带权边界  
  122.         adjMat[start][end] = weight;  
  123.         adjMat[end][start] = weight;  //最优路径的时候不需要这句  
  124.     }  
  125.       
  126.     public void displayVertex(int v) {  
  127.         System.out.print(vertexArray[v].label);  
  128.     }  
  129.       
  130.     /************************ 带权图的最小生成树 ***************************/       
  131.     public void MinSpanningTree() {  
  132.         currentVert = 0//从0开始  
  133.         while(nTree < nVerts-1) { //当不是所有节点都在最小生成树中时  
  134.             vertexArray[currentVert].isInTree = true//将当前顶点加到树中  
  135.             nTree++;  
  136.               
  137.             //往PQ中插入与当前顶点相邻的一些边界  
  138.             for(int i = 0; i < nVerts; i++) {  
  139.                 if(i == currentVert) //如果是本顶点,跳出  
  140.                     continue;  
  141.                 if(vertexArray[i].isInTree) //如果顶点i已经在树中,跳出  
  142.                     continue;  
  143.                 int distance = adjMat[currentVert][i]; //计算当前顶点到i顶点的距离  
  144.                 if(distance == INFINITY)   
  145.                     continue//如果当前顶点与i顶点无穷远,跳出  
  146.                 putInPQ(i, distance); //将i节点加入PQ中  
  147.             }  
  148.               
  149.             if(thePQ.size() == 0) { //如果PQ为空,表示图不连接  
  150.                 System.out.println("Graph not connected!");  
  151.                 return;  
  152.             }  
  153.               
  154.             Edge theEdge = thePQ.removeMin();  
  155.             int sourceVert = theEdge.srcVert;  
  156.             currentVert = theEdge.destVert;  
  157.               
  158.             System.out.print(vertexArray[sourceVert].label);  
  159.             System.out.print(vertexArray[currentVert].label);  
  160.             System.out.print(" ");  
  161.         }  
  162.     }  
  163.   
  164.     private void putInPQ(int newVert, int newDist) {  
  165.         int queueIndex = thePQ.find(newVert);//判断PQ中是否已经有到相同目的顶点的边界  
  166.         if(queueIndex != -1) { //如果有则与当前顶点到目的顶点的距离作比较,保留短的那个  
  167.             Edge tempEdge = thePQ.peekN(queueIndex);//get edge  
  168.             int oldDist = tempEdge.distance;  
  169.             if(oldDist > newDist) { //如果新的边界更短  
  170.                 thePQ.removeN(queueIndex); //删除旧边界  
  171.                 Edge theEdge = new Edge(currentVert, newVert, newDist);  
  172.                 thePQ.insert(theEdge);  
  173.             }  
  174.         }  
  175.         else { //如果PQ中没有到相同目的顶点的边界  
  176.             Edge theEdge = new Edge(currentVert, newVert, newDist);  
  177.             thePQ.insert(theEdge);//直接添加到PQ  
  178.         }  
  179.     }  
  180.       
  181.     /************************** 最短路径问题 ****************************/  
  182.     /** 
  183.      * path()方法执行真正的最短路径算法。 
  184.      */  
  185.     public void path() { //寻找所有最短路径  
  186.         /* 
  187.          * 源点总在vertexArray[]数组下标为0的位置,path()方法的第一个任务就是把这个顶点放入树中。 
  188.          * 算法执行过程中,将会把其他顶点也逐一放入树中。把顶点放入树中的操作是设置一下标志位即可。 
  189.          * 并把nTree变量增1,这个变量记录了树中有多少个顶点。 
  190.          */  
  191.         int startTree = 0//从vertex 0开始  
  192.         vertexArray[startTree].isInTree = true;  
  193.         nTree = 1;  
  194.           
  195.         /* 
  196.          * path()方法把邻接矩阵的对应行表达的距离复制到sPath[]中,实际总是先从第0行复制 
  197.          * 为了简单,假定源点的下标总为0。最开始,所有sPath[]数组中的父节点字段为A,即源点。 
  198.          */  
  199.         for(int i = 0; i < nVerts; i++) {  
  200.             int tempDist = adjMat[startTree][i];  
  201.             //sPath中保存的都是到初始顶点的距离,所以父顶点默认都是初始顶点,后面程序中会将其修改  
  202.             sPath[i] = new DistPar(startTree, tempDist);  
  203.         }  
  204.           
  205.         /* 
  206.          * 现在进入主循环,等到所有的顶点都放入树中,这个循环就结束,这个循环有三个基本动作: 
  207.          * 1. 选择sPath[]数组中的最小距离 
  208.          * 2. 把对应的顶点(这个最小距离所在列的题头)放入树中,这个顶点变成“当前顶点”currentVert 
  209.          * 3. 根据currentVert的变化,更新所有的sPath[]数组内容 
  210.          */  
  211.         while(nTree < nVerts) {  
  212.             //1. 选择sPath[]数组中的最小距离  
  213.             int indexMin = getMin(); //获得sPath中的最小路径值索引  
  214.             int minDist = sPath[indexMin].distance; //获得最小路径  
  215.               
  216.             if(minDist == INFINITY) {  
  217.                 System.out.println("There are unreachable vertices");  
  218.                 break;  
  219.             }  
  220.             //2. 把对应的顶点(这个最小距离所在列的题头)放入树中,这个顶点变成“当前顶点”currentVert  
  221.             else { //reset currentVert  
  222.                 currentVert = indexMin;  
  223.                 startToCurrent = sPath[indexMin].distance;  
  224.             }  
  225.             vertexArray[currentVert].isInTree = true;  
  226.             nTree++;  
  227.             //3. 根据currentVert的变化,更新所有的sPath[]数组内容  
  228.             adjust_sPath();  
  229.         }  
  230.         displayPaths();  
  231.           
  232.         nTree = 0;  
  233.         for(int i = 0; i < nVerts; i++) {  
  234.             vertexArray[i].isInTree = false;  
  235.         }  
  236.     }  
  237.       
  238.     //获取sPath中最小路径的索引  
  239.     private int getMin() {  
  240.         int minDist = INFINITY;  
  241.         int indexMin = 0;  
  242.         for(int i = 0; i < nVerts; i++) {  
  243.             if(!vertexArray[i].isInTree && sPath[i].distance < minDist) {  
  244.                 minDist = sPath[i].distance;  
  245.                 indexMin = i;  
  246.             }  
  247.         }  
  248.         return indexMin;  
  249.     }  
  250.       
  251.     /*调整sPath中存储的对象的值,即顶点到初始顶点的距离,和顶点的父顶点 
  252.      * 这是Dijkstra算法的核心 
  253.      */  
  254.     private void adjust_sPath() {  
  255.         int column = 1;  
  256.         while(column < nVerts) {  
  257.             if(vertexArray[column].isInTree) {  
  258.                 column++;  
  259.                 continue;  
  260.             }  
  261.             int currentToFringe = adjMat[currentVert][column]; //获得当前顶点到其他顶点的距离,其他顶点不满足isInTree  
  262.             int startToFringe = startToCurrent + currentToFringe; //计算其他顶点到初始顶点的距离=当前顶点到初始顶点距离+当前顶点到其他顶点的距离  
  263.             int sPathDist = sPath[column].distance; //获得column处顶点到起始顶点的距离,如果不与初始顶点相邻,默认值都是无穷大  
  264.               
  265.             if(startToFringe < sPathDist) {  
  266.                 sPath[column].parentVert = currentVert; //修改其父顶点  
  267.                 sPath[column].distance = startToFringe; //以及到初始顶点的距离  
  268.             }  
  269.             column++;  
  270.         }  
  271.     }  
  272.     //显示路径  
  273.     private void displayPaths() {  
  274.         for(int i = 0; i < nVerts; i++) {  
  275.             System.out.print(vertexArray[i].label + "=");  
  276.             if(sPath[i].distance == INFINITY)  
  277.                 System.out.print("infinity");  
  278.             else  
  279.                 System.out.print(sPath[i].distance);  
  280.             char parent = vertexArray[sPath[i].parentVert].label;  
  281.             System.out.print("(" + parent + ") ");  
  282.         }  
  283.         System.out.println("");  
  284.     }  
  285.   
  286. }  
    下面是测试用例:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package test;  
  2. import graph.WeightedGraph;  
  3.   
  4. public class Test {   
  5.     public static void main(String[] args){  
  6.         WeightedGraph arr = new WeightedGraph();  
  7.         arr.addVertex('A');  
  8.         arr.addVertex('B');  
  9.         arr.addVertex('C');  
  10.         arr.addVertex('D');  
  11.         arr.addVertex('E');  
  12.           
  13.         arr.addEdge(0150);  //AB 50  
  14.         arr.addEdge(0380);  //AD 80  
  15.         arr.addEdge(1260);  //BC 60  
  16.         arr.addEdge(1390);  //BD 90  
  17.         arr.addEdge(2440);  //CE 40  
  18.         arr.addEdge(3220);  //DC 20  
  19.         arr.addEdge(3470);  //DE 70  
  20.         arr.addEdge(4150);  //EB 50  
  21.           
  22.         arr.MinSpanningTree(); //最小生成树  
  23.         System.out.println(" ");  
  24.         arr.path(); //最短路径  
  25.     }  
  26. }  

    输出结果为

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. AB BE EC CD    
  2. A=infinity(A) B=50(A) C=100(D) D=80(A) E=100(B)   

    带权图就探讨到这吧,如果有问题请留言指正~


转载:http://blog.csdn.net/eson_15/article/details/51143808

目录
相关文章
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
84 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
33 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
2月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
36 4
|
2月前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
22 0
数据结构与算法学习十四:常用排序算法总结和对比
|
2月前
|
存储 缓存 分布式计算
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
这篇文章是关于数据结构与算法的学习指南,涵盖了数据结构的分类、数据结构与算法的关系、实际编程中遇到的问题以及几个经典的算法面试题。
37 0
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
|
2月前
|
机器学习/深度学习 搜索推荐 算法
探索数据结构:初入算法之经典排序算法
探索数据结构:初入算法之经典排序算法
|
2月前
|
算法 Java 索引
数据结构与算法学习十五:常用查找算法介绍,线性排序、二分查找(折半查找)算法、差值查找算法、斐波那契(黄金分割法)查找算法
四种常用的查找算法:顺序查找、二分查找(折半查找)、插值查找和斐波那契查找,并提供了Java语言的实现代码和测试结果。
25 0
|
27天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
123 9
|
18天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
23 1
|
6天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
28 5