算法10 之带权图

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

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

最小生成树问题

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

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

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

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

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

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

[java] view
plain
 copy

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

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

1. 当前顶点放在树中。

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

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

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

1.源点和终点相同;

2. 终点在树中;

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

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

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

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

最短路径问题

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

为了实现这个算法,首先得建一个辅助类DistPar类,这个类中封装了到初始顶点的距离以及父顶点的信息。

[java] view
plain
 copy

  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. public DistPar(int pv, int d) {
  6. distance = d;
  7. parentVert = pv;
  8. }
  9. }

另外还得有个数组,这是最短路径算法中的一个关键数据结构,它保持了从源点到其他顶点(终点)的最短路径。在算法的执行过程中这个距离是变化的,知道最后,它存储了从源点开始的真正最短距离。这个数组定义为WeightedGraph的一个私有成员变量:

[java] view
plain
 copy

  1. private DistPar[] sPath; //存储最短路径数据,存储的是上面的DistPar对象
  2. private int startToCurrent; //到当前顶点的距离

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

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

[java] view
plain
 copy

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

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

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

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

完整代码

下面附上有权图的完整代码和测试代码

[java] view
plain
 copy

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

下面是测试用例:

[java] view
plain
 copy

  1. package test;
  2. import graph.WeightedGraph;
  3. public class Test {
  4. public static void main(String[] args){
  5. WeightedGraph arr = new WeightedGraph();
  6. arr.addVertex(‘A‘);
  7. arr.addVertex(‘B‘);
  8. arr.addVertex(‘C‘);
  9. arr.addVertex(‘D‘);
  10. arr.addVertex(‘E‘);
  11. arr.addEdge(0, 1, 50);  //AB 50
  12. arr.addEdge(0, 3, 80);  //AD 80
  13. arr.addEdge(1, 2, 60);  //BC 60
  14. arr.addEdge(1, 3, 90);  //BD 90
  15. arr.addEdge(2, 4, 40);  //CE 40
  16. arr.addEdge(3, 2, 20);  //DC 20
  17. arr.addEdge(3, 4, 70);  //DE 70
  18. arr.addEdge(4, 1, 50);  //EB 50
  19. arr.MinSpanningTree(); //最小生成树
  20. System.out.println(" ");
  21. arr.path(); //最短路径
  22. }
  23. }

输出结果为:

[java] view
plain
 copy

  1. AB BE EC CD
  2. A=infinity(A) B=50(A) C=100(D) D=80(A) E=100(B)
时间: 2024-08-06 07:28:13

算法10 之带权图的相关文章

带权图的最短路径算法(Dijkstra)实现

一,介绍 本文实现带权图的最短路径算法.给定图中一个顶点,求解该顶点到图中所有其他顶点的最短路径 以及 最短路径的长度.在决定写这篇文章之前,在网上找了很多关于Dijkstra算法实现,但大部分是不带权的.不带权的Dijkstra算法要简单得多(可参考我的另一篇:无向图的最短路径算法JAVA实现):而对于带权的Dijkstra算法,最关键的是如何“更新邻接点的权值”.本文采用最小堆主辅助,以重新构造堆的方式实现更新邻接点权值. 对于图而言,存在有向图和无向图.本算法只需要修改一行代码,即可同时实

无向带权图的最小生成树算法——Prim及Kruskal算法思路

边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用:要连通n个城市需要n-1条边线路.可以把边上的权值解释为线路的造价.则最小生成树表示使其造价最小的生成树. 构造网的最小生成树必须解决下面两个问题: 1.尽可能选取权值小的边,但不能构成回路: 2.选取n-1条恰当的边以连通n个顶点: MST性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集.若(u,v)是一条具有最小权值的

用无向带权图实现校园导航系统

学校数据结构的课程实验之一. 用到的数据结构:无向带权图 用到的算法:Floyd最短路径算法,深度优先搜索(递归实现) 需求分析: 设计一个校园导航程序,为访客提供各种信息查询服务: 1. 以图中各顶点表示校内各单位地点,存放单位名称,代号,简介等信息:以边表示路径,存放路径长度等相关信息. 2. 图中任意单位地点相关信息的查询. 3. 图中任意单位的问路查询,即查询任意两个单位之间的一条最短的路径. 2. 从图中任意单位地点出发的一条深度优先遍历路径. 主函数: 1 #include <ios

有向无环带权图的最短路径及长度

给定一个有向无环图的拓扑序列,获取这个序列从起点到序列最后一点的最短路径. 起点默认为0点(顶点为0,1,2...和数组索引对应),序列通过拓扑排序获取. 下面给出实现,首先是对一个有向无环图进行拓扑排序的类. package graphics.dag.topologicalsort; /** * 获取一个拓扑序列 * @author zhangxinren * */ public class TopologicalSort { // 每条边的入度 private static int[] in

Java数据结构——带权图

带权图的最小生成树——Prim算法和Kruskal算法 带权图的最短路径算法——Dijkstra算法 package graph; // path.java // demonstrates shortest path with weighted, directed graphs 带权图的最短路径算法 // to run this program: C>java PathApp ////////////////////////////////////////////////////////////

邻接矩阵表示有向带权图

#include <stdio.h> #include <stdlib.h> #include <string.h> typedef char VertexType[5]; //存储顶点值 #define MaxSize 50 #define INIT 10000 typedef struct //邻接矩阵,存储弧的信息 {     int adj; }ArcNode,AdjMatrix[MaxSize][MaxSize]; typedef struct   //图的类

[算法] 带权图

最小生成树(Minimum Span Tree):对于带权无向连通图.所有节点都连通且总权值最小.应用:电缆布线.网络.电路设计 找V-1条边,连接V个顶点,总权值最小 切分定理(Cut Property):给定任意切分,横切边中权值最小的边必属于最小生成树 切分:把图中节点分为两部分 横切边:边的两个端点属于切分的不同两边 证明:反证法,假设横切边中一条权值不是最小的边属于最小生成树,给生成树添加横切边中权值最小的边形成环,删掉权值不是最小的边打破环得到新的权值更小的生成树,与假设矛盾 实现:

UVA1599带权图的最短路径以及边的保存方式

这道题目我一开始的想法是还是按照保存父节点的基本思路,但是每次的时候需要挑选每个节点中权值最小的那条边对应的那个点 但是这样会有问题: 第一个问题是:如果某个节点有两条边的权值都是一样的,那么这两个对应的点都必须要入队,那么问题就出现了,如果这俩个节点对应的下一个节点是同一个节点,但是先入队的那个的对应边的权值比较大,后入队的那条边对应的权值比较小,那么在前一个节点访问设置vis后,后面那个节点就不可以再访问了. 其实上面这个情况还是最简单的情况,还有很多更复杂的情况没有考虑到. 所以需要使用刘

为什么Prim算法不适用于带权有向图

其实,能不能使用Prim算法计算图的最小生成树,和这个图是有向图还是无向图,这两者之间没有必然的联系. 而是,如果在有向图中出现了以下情况,那么就不能使用Prim算法: 假设一个有向图有3个顶点1->2 8,1->3 8,2->3 4,3->2 3 四条边,由于1->2和1->3是相等的所以prim因为循环顺序的原因最后结果构造出来的树是1->2->3但是答案应该是1->3->2 之所以造成这样的情况,原因只有一个,那就是,在有向图中,有可能存在