美文网首页
具体算法7 - A*启发式搜索

具体算法7 - A*启发式搜索

作者: 天命_风流 | 来源:发表于2020-04-19 00:34 被阅读0次

    A* 启发式搜索算法是对 Dijkstra 算法的改进版本,它和后者的主要差别在于,加入了到终点的距离量化,使得 A* 算法不会像 Dijkstra 算法那样“跑偏”。

    问题分析

    之前我们介绍过 Dijkstra算法(下面简写成 D算法),不知道你有没有发现这样一个问题:当地图特别大的时候,D算法 的执行时间会非常长,为了确定自己找到的是最短路径,算法执行了大量的“跑偏”计算。

    D算法有点儿类似 BFS算法 ,它每次回寻找与当前节点最近的顶点,向外扩展。但是,这种扩展思路其实比较盲目,这里举个例子: 地图.jpg

    在 D算法 的实现过程中,最先被搜索到的顶点依次是 1,2,3。很明显,这种搜索方向“跑偏了”。

    在工程中,不一定要找到问题的最优解,我们可以一定程度地接受次优解,尤其是这个次优解会节省大量计算资源的时候。基于这样的理念,A* 算法 应运而生。

    算法解析

    在 A* 算法中,除了当前顶点和起始顶点的距离 g(i) ( i 为顶点编号),还要考虑当前顶点到终点的 直线距离 (注意,是直线距离,不是路径距离) h(i) ,这个距离的专业叫法是 启发函数 。这个距离非常好计算,我们可以用 “勾股定理” 轻松求出,但是为了减少计算机的计算量,这里选择 曼哈顿距离 ,其计算方式如下:

    int hManhattan(Vertex v1, Vertex v2) { // Vertex表示顶点,后面有定义
      return Math.abs(v1.x - v2.x) + Math.abs(v1.y - v2.y);
    }
    

    有了启发函数,我们可以使用 估值函数 f(i) 代替 D算法 中的 dist数组 ,它的计算如下:

    f( i ) = g( i ) + h( i )

    根据这个定义,我们需要修改在 D算法 中的 vertex:

    private class Vertex {
      public int id; // 顶点编号ID
      public int dist; // 从起始顶点,到这个顶点的距离,也就是g(i)
      public int f; // 新增:f(i)=g(i)+h(i)
      public int x, y; // 新增:顶点在地图中的坐标(x, y)
      public Vertex(int id, int x, int y) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.f = Integer.MAX_VALUE;
        this.dist = Integer.MAX_VALUE;
      }
    }
    // Graph类的成员变量,在构造函数中初始化
    Vertex[] vertexes = new Vertex[this.v];
    // 新增一个方法,添加顶点的坐标
    public void addVetex(int id, int x, int y) {
      vertexes[id] = new Vertex(id, x, y)
    }
    

    A* 算法的主要代码实现在下面,这里要说明它和 D算法 的三点区别:

    • 优先级队列的构建不同:A* 根据 f 构建优先队列,而 D算法根据 dist(也就是 g)构建优先队列;
    • A* 会在更新 dist 的时候同步更新 f 值;
    • 循环结束条件不同:D算法 在终点出队列的时候结束,A* 在遍历到终点就结束。
    public void astar(int s, int t) { // 从顶点s到顶点t的路径
      int[] predecessor = new int[this.v]; // 用来还原路径
      // 按照vertex的f值构建的小顶堆,而不是按照dist
      PriorityQueue queue = new PriorityQueue(this.v);
      boolean[] inqueue = new boolean[this.v]; // 标记是否进入过队列
      vertexes[s].dist = 0;
      vertexes[s].f = 0;
      queue.add(vertexes[s]);
      inqueue[s] = true;
      while (!queue.isEmpty()) {
        Vertex minVertex = queue.poll(); // 取堆顶元素并删除
        for (int i = 0; i < adj[minVertex.id].size(); ++i) {
          Edge e = adj[minVertex.id].get(i); // 取出一条minVetex相连的边
          Vertex nextVertex = vertexes[e.tid]; // minVertex-->nextVertex
          if (minVertex.dist + e.w < nextVertex.dist) { // 更新next的dist,f
            nextVertex.dist = minVertex.dist + e.w;
            nextVertex.f 
               = nextVertex.dist+hManhattan(nextVertex, vertexes[t]);
            predecessor[nextVertex.id] = minVertex.id;
            if (inqueue[nextVertex.id] == true) {
              queue.update(nextVertex);
            } else {
              queue.add(nextVertex);
              inqueue[nextVertex.id] = true;
            }
          }
          if (nextVertex.id == t) { // 只要到达t就可以结束while了
            queue.clear(); // 清空queue,才能推出while循环
            break; 
          }
        }
      }
      // 输出路径
      System.out.print(s);
      print(s, t, predecessor); // print函数请参看Dijkstra算法的实现
    }
    

    这个算法比 D算法快速,但是A* 算法无法获得最短路径

    如果我想获得最短路径,最笨的办法是使用回溯算法:

    回溯穷举.jpg

    动态规划(D算法),可以通过剪枝减少计算:

    Dijkstra剪枝.jpg

    而 A* 算法 使用了贪婪策略,它选择最小的 f 值 作为路径选择的依据,且在第一次遇到终点的时候就结束,这使得 A* 算法没有考察其它路线(实际上这也是它更少耗费资源的原因),也就不可能找出最短路径了。

    总结

    • 启发式搜索算法通过利用估价函数避免“跑偏”
    • A* 使用了贪婪策略,通过节课的例子,我们有一个新的认识:在工程中可以允许次优解的出现(如果要找到最优解会耗费大量资源的话),而寻找次优解的一大途径就是使用 贪婪策略
    • 所以,我们可以笼统地对三种算法的资源耗费进行排序:回溯>动态规划>贪婪

    以上就是全部内容

    注:本文章的主要内容来自我对极客时间app的《数据结构与算法之美》专栏的总结,我使用了大量的原文、代码和截图,如果想要了解具体内容,可以前往极客时间

    相关文章

      网友评论

          本文标题:具体算法7 - A*启发式搜索

          本文链接:https://www.haomeiwen.com/subject/njstbhtx.html