当前位置:首页 >探索 >图算法系列之计算图中最短路径 图算图中作者Silently9527

图算法系列之计算图中最短路径 图算图中作者Silently9527

2024-06-25 19:55:04 [百科] 来源:避面尹邢网

图算法系列之计算图中最短路径

作者: Silently9527 网络 通信技术 算法 在前面两篇中我们通过深度优先搜索可以从图中找出一条通过顶点v到顶点w的图算图中路径,但是法系深度优先搜索与顶点的输入有很大的关系,找出来的列之路径路径也不一定是最短的,通常情况下我们很多时候需要找出图中的计算最短路径,比如:地图功能。最短

[[398324]]

本文转载自微信公众号「贝塔学JAVA」,图算图中作者Silently9527。法系转载本文请联系贝塔学JAVA公众号。列之路径

前言

在前面两篇中我们通过深度优先搜索可以从图中找出一条通过顶点v到顶点w的计算路径,但是最短深度优先搜索与顶点的输入有很大的关系,找出来的图算图中路径也不一定是最短的,通常情况下我们很多时候需要找出图中的法系最短路径,比如:地图功能。列之路径这里我们就需要使用到广度优先搜索算法

图算法系列之计算图中最短路径 图算图中作者Silently9527

广度优先搜索

依然使用之前定义的计算寻找路径的API

图算法系列之计算图中最短路径 图算图中作者Silently9527

  1. public class Paths {  
  2.     Paths(Graph graph, int s); 
  3.      
  4.     boolean hasPathTo(int v); //判断出从s->v是否存在路径 
  5.      
  6.     Iterable<Integer> pathTo(int v); //如果存在路径,返回路径 

在广度优先搜索中,最短为了找出最短路径,我们需要按照起点的顺序来遍历所有的顶点,而不在是使用递归来实现;算法的思路:

图算法系列之计算图中最短路径 图算图中作者Silently9527

  • 使用队列来保存已经被标记过但是邻接表还未被遍历过的顶点
  • 取出队列中的下一个顶点v并标记它
  • 将v相邻的所有未被标记的顶点加入到队列

在该算法中,为了保存路径,我们依然需要使用一个边的数组edgeTo[],用一颗父链树来表示根节点到所有连通顶点的最短路径。

  1. public class BreadthFirstPaths {  
  2.     private boolean marked[]; 
  3.     private int[] edgeTo; 
  4.     private int s; 
  5.     private Queue<Integer> queue = new LinkedListQueue<>(); 
  6.  
  7.     public BreadthFirstPaths(Graph graph, int s) {  
  8.         this.s = s; 
  9.         this.marked = new boolean[graph.V()]; 
  10.         this.edgeTo = new int[graph.V()]; 
  11.  
  12.         bfs(graph, s); 
  13.     } 
  14.  
  15.     private void bfs(Graph graph, int s) {  
  16.         this.marked[s] = true; 
  17.         this.queue.enqueue(s); 
  18.         while (!this.queue.isEmpty()) {  
  19.             Integer v = this.queue.dequeue(); 
  20.             for (int w : graph.adj(v)) {  
  21.                 if (!this.marked[w]) {  
  22.                     this.marked[w] = true; 
  23.                     this.edgeTo[w] = v; 
  24.                     this.queue.enqueue(w); 
  25.                 } 
  26.             } 
  27.         } 
  28.  
  29.  
  30.     } 
  31.  
  32.     public boolean hasPathTo(int v) {  
  33.         return this.marked[v]; 
  34.     } 
  35.  
  36.     public Iterable<Integer> pathTo(int v) {  
  37.         if (!hasPathTo(v)) {  
  38.             throw new IllegalStateException("s不能到达v"); 
  39.         } 
  40.         Stack<Integer> stack = new LinkedListStack<>(); 
  41.         stack.push(v); 
  42.         while (edgeTo[v] != s) {  
  43.             stack.push(edgeTo[v]); 
  44.             v = edgeTo[v]; 
  45.         } 
  46.         stack.push(s); 
  47.         return stack; 
  48.     } 

以下图为列,来看看广度优先搜索的运行轨迹

 

单元测试的代码:

  1. @Test 
  2. public void test() {  
  3.     Graph graph = new Graph(8); 
  4.     graph.addEdge(0, 1); 
  5.     graph.addEdge(0, 2); 
  6.     graph.addEdge(0, 5); 
  7.     graph.addEdge(1, 3); 
  8.     graph.addEdge(2, 4); 
  9.     graph.addEdge(4, 3); 
  10.     graph.addEdge(5, 3); 
  11.     graph.addEdge(6, 7); 
  12.  
  13.     BreadthFirstPaths paths = new BreadthFirstPaths(graph, 0); 
  14.     System.out.println(paths.hasPathTo(5)); 
  15.     System.out.println(paths.hasPathTo(2)); 
  16.     System.out.println(paths.hasPathTo(6)); 
  17.  
  18.     paths.pathTo(5).forEach(System.out::print); 
  19.     System.out.println(); 
  20.     paths.pathTo(4).forEach(System.out::print); 
  21.     System.out.println(); 
  22.     paths.pathTo(2).forEach(System.out::print); 
  23.     System.out.println(); 
  24.     paths.pathTo(3).forEach(System.out::print); 
  25.  

执行的结果与我们分析的运行轨迹一致

符号图

最近几篇文章一起学习到的图算法都是以数字作为了顶点,是因为数字来实现这些算法会非常的简洁方便,但是在实际的场景中,通常都是使用的字符作为顶点而非数字,比如:地图上的位置、电影与演员的关系。

为了满足实际的场景,我们只需要在数字与字符串的关系做一个映射,此时我们会想到之前文章实现的map(通过二叉树实现map、红黑树实现map、哈希表实现map等等,有兴趣的同学可以去翻翻),使用Map来维护字符串和数字的映射关系。

  1. public interface SymbolGraph {  
  2.     boolean contains(String key); //判断是否存在顶点 
  3.  
  4.     int index(String key); //通过名称返回对应的数字顶点 
  5.  
  6.     String name(int v); //通过数字顶点返回对应的字符名称 
  7.  
  8.     Graph graph(); 

实现的思路:

  • 使用Map来保存字符串-数字的映射,key为字符串,value为数字
  • 使用一个数组来反向映射数字-字符串,数组的下标对应数字顶点,值对应字符串名称
  • 假设构造图的顶点与边是通过字符串来表示的,如:a,b,c,d\nb,a,h,l,p\ng,s,z 使用\n分隔的每段第一个字符串表示顶点v,后面的表示与顶点v相连的相邻顶点;

实际的过程可以根据具体情况来确定,不一定非得这种字符串,可以来源于数据库,也可以来源于网路的请求。

代码实现如下:

  1. public class SymbolGraph {  
  2.     private Map<String, Integer> map = new RedBlack23RedBlackTreeMap<>(); 
  3.     private String[] keys; 
  4.     private Graph graph; 
  5.  
  6.     public SymbolGraph(String text) {  
  7.         Arrays.stream(text.split("\n")).forEach(line -> {  
  8.             String[] split = line.split(","); 
  9.             for (int i = 0; i < split.length; i++) {  
  10.                 map.put(split[i], i); 
  11.             } 
  12.         }); 
  13.  
  14.         this.keys = new String[map.size()]; 
  15.         map.keys().forEach(key -> {  
  16.             this.keys[this.map.get(key)] = key; 
  17.         }); 
  18.  
  19.         this.graph = new Graph(this.keys.length); 
  20.         Arrays.stream(text.split("\n")).forEach(line -> {  
  21.             String[] split = line.split(","); 
  22.             int v = this.map.get(split[0]); 
  23.             for (int i = 1; i < split.length; i++) {  
  24.                 this.graph.addEdge(v, this.map.get(split[i])); 
  25.             } 
  26.         }); 
  27.          
  28.     } 
  29.  
  30.     public boolean contains(String key) {  
  31.         return map.contains(key); 
  32.     } 
  33.  
  34.     public int index(String key) {  
  35.         return map.get(key); 
  36.     } 
  37.  
  38.     public String name(int index) {  
  39.         return this.keys[index]; 
  40.     } 
  41.  
  42.     public Graph graph() {  
  43.         return this.graph; 
  44.     } 
  45.  
  46.     public static void main(String[] args) {  
  47.         System.out.println(Arrays.toString("323\n2323".split("\n"))); 
  48.     } 

 

文中所有源码已放入到了github仓库:https://github.com/silently9527/JavaCore

 

责任编辑:武晓燕 来源: 贝塔学JAVA 图算法路径顶点

(责任编辑:综合)

    推荐文章
    热点阅读