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

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的《数据结构与算法之美》专栏的总结,我使用了大量的原文、代码和截图,如果想要了解具体内容,可以前往极客时间

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容