title: 图中的路径和最短路‘
typora-root-url: 图中的路径和最短路‘
date: 2023-07-31 08:44:51
categories: 算法
tags: [算法,图论]
图中的路径和最短路
在上一节中,讲解了图的邻接表和邻接矩阵表示法。这一节来看看,图的路径与最小路问题
最短路径问题是图论中常见的一个问题,主要是要找到在图中两个节点之间的最短路径,其中路径的定义可以根据具体情况而定。
在无权图中,最短路径是指两个节点之间经过的边数量最少的路径。可以使用广度优先搜索算法(BFS)来解决这类问题,该算法从起始节点开始逐层扩展,直到找到目标节点。
在有权图中,每条边都有一个权重值,最短路径问题就变成了找到两个节点之间权重和最小的路径。可以使用迪杰斯特拉算法(Dijkstra’s algorithm)或A算法来解决这类问题。迪杰斯特拉算法从起始节点开始,逐步更新到达其他节点的最短距离,直到找到目标节点的最短路径。A算法是一种启发式搜索算法,结合了迪杰斯特拉算法和一定的启发式函数,能更高效地找到最短路径。
除了以上提到的两种算法,还有其他一些算法可以用于解决最短路径问题,具体算法的选择取决于图的特点和需求。
无权图的最短路径算法
对于无权图的最短路径问题,可以使用广度优先搜索算法(BFS)来求解。以下是使用BFS算法解决无权图最短路径问题的基本步骤:
-
选择一个起始节点作为搜索的起点。
-
将起始节点加入到一个队列中,并将其标记为已访问。
-
循环执行以下步骤直到队列为空:
a. 从队列中取出一个节点作为当前节点。
b. 遍历当前节点的所有邻居节点:
- 如果邻居节点未被访问过,则将其加入队列,并将其标记为已访问。
- 同时记录该邻居节点的父节点,以便在最后重构路径时使用。
-
重构路径:从目标节点开始,通过父节点逐步回溯到起始节点,即可得到最短路径。
BFS算法通过逐层搜索,确保了首次到达目标节点的路径一定是最短路径。在实现过程中,可以利用一个字典或数组来记录节点的父节点,以方便路径的重构。
获取无权图的最短路径表
-
代码
/** * 获取无权图的最短路径表 * 返回类型 为一个 int[vertices][3] 数组 * int[i][0] 为是否能访问到(1.能,0.不能) * int[i][1] 距离 * int[i][1] 到目的节点的前一个节点,-1代表没有前一个节点 * @param origin * @return */ public int[][] getShortestPathTable(int origin){ if(origin>=vertices){ return null; } int[][] res = new int[vertices][3]; Queue<Integer> queue = new LinkedList<>(); res[origin-1][0] = 1; res[origin-1][1] = 0; res[origin-1][2] = -1; queue.offer(origin-1); while (!queue.isEmpty()){ int newNode = queue.poll(); for (int i = 0; i < vertices; i++) { if (matrix[newNode][i]!=0){ if (res[i][0]!=1){ queue.offer(i); res[i][0] = 1; res[i][1] = res[newNode][1]+1; res[i][2] = newNode; } } } } return res; } public void shortestTableToString(int[][] shortTable){ System.out.println("vertex\tvisit\tdist\tpath\t"); for (int i = 0; i < vertices; i++) { String vertex = "v"+i; String visit = shortTable[i][0]==0?"false":"true"; int dist = shortTable[i][1]; String path = "v"+shortTable[i][2]; System.out.printf("%s\t%s\t%d\t%s\t\n",vertex,visit,dist,path); } }
-
测试代码
public static void main(String[] args) { Graphs graphs = new Graphs(); graphs.generate(); int[][] shortTable=graphs.getShortestPathTable(3); graphs.shortestTableToString(shortTable); }
-
测试结果
根据最短路径表获取最短路径
-
代码
public void shortestPathPrint(int[] path){ int n = path.length; if (n==0){ return; } System.out.print(path[0]); for (int i = 1; i <n ; i++) { System.out.print("-->"+path[i]); } System.out.println(); } public int[] getShortestPathBytable(int origin,int end,int[][] shortTable){ if (end>=vertices||end<0||origin>end||origin<0){ return null; } if (shortTable[end][0]==0){ return null; } List<Integer> list = new ArrayList<>(); int newNode = end; while (shortTable[newNode][2] != -1){ list.add(newNode); newNode = shortTable[newNode][2]; } list.add(origin); int size = list.size(); int[] res = new int[list.size()]; for (int i = 0;i<size;i++){ res[i] = list.get(size-i-1); } return res; }
-
测试代码
public static void main(String[] args) { Graphs graphs = new Graphs(); graphs.generate(); int[][] shortTable=graphs.getShortestPathTable(0); graphs.shortestTableToString(shortTable); int[] shortestPath = graphs.getShortestPathBytable(0,6,shortTable); graphs.shortestPathPrint(shortestPath); }
-
结果
有权图的最短路径
有权图的最短路径问题可以使用一些经典算法来解决,最常用的算法是Dijkstra算法和Bellman-Ford算法。这两种算法都可以找到一个顶点到图中其他顶点的最短路径。
1. Dijkstra算法:
Dijkstra算法,由荷兰计算机科学家Edsger W. Dijkstra在1956年提出,是一种解决单源最短路径问题的算法。该算法是以图的顶点之间的边权重来计算最短路径的。
Dijkstra算法的基本思想是从起点开始,逐步确定到达各个顶点的最短路径。算法维护一个距离表,记录从起点到各个顶点的最短距离。初始化时,起点的最短距离为0,其他顶点的最短距离为无穷大。
算法的步骤如下:
- 初始化距离表和访问表。将起点标记为已访问,起点的最短距离为0,其他顶点的最短距离为无穷大。
- 选择距离表中未访问的顶点中最短距离的顶点作为当前顶点。
- 对当前顶点的邻居顶点,计算经由当前顶点到达邻居顶点的距离,并更新距离表中的最短距离。
- 更新完邻居顶点的最短距离后,将当前顶点标记为已访问。
- 重复步骤2至4,直到所有顶点都被访问过或者没有可达的顶点。
通过不断更新距离表,Dijkstra算法可以找到起点到其他顶点的最短路径。该算法的时间复杂度为O(V^2),其中V为顶点数。可以使用优先队列(如最小堆)来优化算法的时间复杂度至O((E+V)logV),其中E为边数。
-
代码
/** * 获取无权图的最短路径表 * 返回类型 为一个 int[vertices][3] 数组 * int[i][0] 为是否能访问到(1.能,0.不能) * int[i][1] 距离 * int[i][2] 到目的节点的前一个节点,-1代表没有前一个节点 * @param origin * @return */ public int[][] getShortestPathTableDijkstra(int origin){ if(origin>=vertices){ return null; } int[][] res = new int[vertices][3]; for(int i = 0;i<vertices;i++){ res[i][1] = Integer.MAX_VALUE/2; } res[origin][0] = 1; res[origin][1] = 0; res[origin][2] = -1; int newNode = origin; while (newNode>=0){ for (int i = 0; i < vertices; i++) { if (matrix[newNode][i]!=0){ if (matrix[newNode][i]+res[newNode][1]<res[i][1]){ res[i][0] = 0; res[i][1] = matrix[newNode][i]+res[newNode][1]; res[i][2] = newNode; } } } res[newNode][0] = 1; newNode = findMinDistance(res); } return res; } public int findMinDistance(int[][] res) { int minValue = Integer.MAX_VALUE; int minIndex = -1; for (int i = 0; i < vertices; i++) { if (res[i][0] == 0){ if (minIndex == -1||res[i][1]<minValue){ minValue = res[i][1]; minIndex = i; } } } return minIndex; }
-
测试结果
2. Bellman-Ford算法:
Bellman-Ford算法是解决单源最短路径问题的另一种算法,它可以处理含有负权边的图。Bellman-Ford算法的基本原理是通过迭代更新图中所有边的最短距离估计值,直到达到最优解。
Bellman-Ford算法的步骤如下:
- 初始化距离表,将起点的最短距离设为0,其他顶点的最短距离设为无穷大。
- 重复以下过程,执行V-1次:
a. 对图中的每一条边,计算通过该边到达目标顶点的距离估计,并将其与当前最短距离进行比较。如果该距离估计小于当前最短距离,则更新最短距离。 - 检查图中的负权回路。对图中的每一条边,如果通过该边能够进一步减小最短距离,则说明存在负权回路,即图中存在无穷小的最短距离。
Bellman-Ford算法的时间复杂度为O(V*E),其中V是顶点数,E是边数。因为算法要进行V-1次迭代,每次迭代需要遍历所有的边。