返回

贪心搜索算法与A*算法详解及路径规划对比

Ai

贪心搜索算法详解及 A* 算法对比:解决路径规划难题

最近接触人工智能,发现一个关于贪心搜索算法的练习题有点搞不明白,借此机会把学习和解决问题的过程记录下来,希望能对同样刚入门的朋友们有所帮助。

问题重现

题目给出了一个图,如图所示。每个节点内的数值代表从该节点到目标节点 G 的启发式成本(heuristic cost),边上的数值代表两个节点之间的路径成本(path cost)。

图 1

要求:

  1. 如果 B 是起始节点,G 是目标节点,分别使用贪心搜索算法和 A* 搜索算法找出路径。
  2. 根据第 1 问的结果,证明贪心搜索算法不是最优的。

什么是贪心搜索算法?

贪心搜索算法是一种在每一步选择中都采取当前状态下最优决策的算法,它试图通过一系列局部最优选择来达到全局最优解(但通常无法保证全局最优)。在路径规划中,贪心搜索会优先选择 看起来 距离目标最近的节点。这里的“看起来” 通常使用启发式函数(heuristic function)来评估。

贪心搜索的原理

对于每个节点,贪心搜索算法只关注从当前节点到目标节点的估计成本(即启发式成本),而不考虑从起点到当前节点的实际成本。每次都选择启发式成本最低的相邻节点作为下一个访问的节点。

如何解决本题中的贪心搜索?

根据题意, 起点是 B, 终点是 G.

  1. 从 B 出发:

    • B 的邻居节点是 A, D, E。
    • A 的启发式成本是 11。
    • D 的启发式成本是 5。
    • E 的启发式成本是 8。
    • 选择 D (因为 5 最小)。
  2. 从 D 出发:

    • D 的邻居节点是 C, E, G。
    • C 的启发式成本是 10。
    • E 的启发式成本是 8。
    • G 的启发式成本是 0。
    • 选择 G (因为 0 最小)。
  3. 到达 G, 结束。

因此, 贪心搜索算法找到的路径是: B -> D -> G 。 这条路径的总成本是: 11 + 6 = 17。

// 代码演示(非运行代码,仅作概念说明)
// 贪心搜索的伪代码
function GreedySearch(startNode, goalNode, heuristicFunction, graph) {
  currentNode = startNode;
  path = [currentNode];

  while (currentNode != goalNode) {
    neighbors = graph.getNeighbors(currentNode);
    bestNeighbor = null;
    minHeuristicCost = Infinity;

    for (neighbor in neighbors) {
      heuristicCost = heuristicFunction(neighbor, goalNode);
      if (heuristicCost < minHeuristicCost) {
        minHeuristicCost = heuristicCost;
        bestNeighbor = neighbor;
      }
    }
    if (bestNeighbor == null) return "failure"; // No path.

    path.push(bestNeighbor);
    currentNode = bestNeighbor;
  }
    return path.
}

什么是 A* 搜索算法?

A* 搜索算法是一种广泛使用的路径规划算法,它综合考虑了从起始节点到当前节点的实际成本 (g(n)) 和从当前节点到目标节点的估计成本 (h(n))。它通过评估函数 f(n) = g(n) + h(n) 来选择下一个要访问的节点,目标是找到一条总成本最低的路径。

A* 算法的原理

A* 算法维护一个开放列表(open list)和一个关闭列表(closed list)。开放列表包含待评估的节点,关闭列表包含已经评估过的节点。 算法每次从开放列表中选择 f(n) 值最小的节点进行扩展,将其相邻节点加入开放列表,并将该节点加入关闭列表。这个过程一直持续到目标节点被选中或开放列表为空。

如何解决本题中的 A* 搜索?

还是以 B 为起点,G 为终点。

我们需要计算每个节点的 f(n) = g(n) + h(n)。 其中:

  • g(n): 从起始节点 B 到当前节点 n 的实际路径成本。
  • h(n): 从当前节点 n 到目标节点 G 的启发式成本 (节点内标的数字)。
  1. 初始状态:

    • Open list: {B (f=0+9=9)}
    • Closed list: {}
  2. 扩展 B:

    • A: f = 6 + 11 = 17
    • D: f = 11 + 5 = 16
    • E: f = 5 + 8 = 13
    • Open list: {A(17), D(16), E(13)}
    • Closed list: {B(9)}
  3. 扩展 E (因为 13 最小):

    • C: f = 5+7+10=22
    • D: f = 5+3+5 = 13
    • G: f = 5+9+0=14
    • Open list: {A(17), D(13),C(22),G(14)}
    • Closed list: {B(9), E(13)}
  4. 扩展 D (因为有两个D,取首次计算出来的, 此时为closed list里面的,为了方便显示,这里不再增加节点)

    • C: f = 11+2+10 = 23.
    • E: f = 11 + 3+ 8 =22.
    • G: f = 11 + 6 +0=17.
    • Open List: {A(17), C(22), G(14)}
    • Closed List: {B(9), E(13),D(16)}
  5. ** 扩展G,达到目标节点**

  • 此时有两条到G的路径
  • B-> E -> G, f =14.
  • B-> D ->G, f = 17

选择f最小的, 因此,A* 搜索算法找到的路径是: B -> E -> G , 这条路径的总成本是: 5 + 9 = 14。

// 代码演示(非运行代码,仅作概念说明)
// A* 搜索的伪代码
function AStarSearch(startNode, goalNode, heuristicFunction, graph) {
  openList = new PriorityQueue(); // 使用优先队列存储open list,f值小的优先级高
  openList.add(startNode, 0);

  cameFrom = {};  // 记录路径
  gScore = {};   // 存储每个节点的g值
  gScore[startNode] = 0;
  fScore = {};   // 存储每个节点的f值
  fScore[startNode] = heuristicFunction(startNode, goalNode);

  while (!openList.isEmpty()) {
    currentNode = openList.poll(); // 取出f值最小的节点

    if (currentNode == goalNode) {
      return reconstructPath(cameFrom, currentNode);
    }

    for (neighbor in graph.getNeighbors(currentNode)) {
      tentativeGScore = gScore[currentNode] + graph.getEdgeCost(currentNode, neighbor);

      if (!(neighbor in gScore) || tentativeGScore < gScore[neighbor]) {
        cameFrom[neighbor] = currentNode;
        gScore[neighbor] = tentativeGScore;
        fScore[neighbor] = tentativeGScore + heuristicFunction(neighbor, goalNode);
        if (!openList.contains(neighbor)) {
          openList.add(neighbor, fScore[neighbor]);
        }
      }
    }
  }

  return "failure"; // 无解
}

function reconstructPath(cameFrom, current) {
   //根据cameFrom 提供的前置节点进行追溯.
    totalPath = [current];
    while (current in cameFrom) {
        current = cameFrom[current];
        totalPath.unshift(current);
    }
    return totalPath;
}

为什么贪心搜索不是最优的?

对比一下两次搜索的结果:

  • 贪心搜索: B -> D -> G, 总成本 = 17。
  • A* 搜索: B -> E -> G, 总成本 = 14。

可以清楚地看到,贪心搜索找到的路径 (B -> D -> G) 并不是成本最低的路径。A* 搜索算法找到了更优的路径 (B -> E -> G)。 这说明贪心搜索算法并不总能保证找到最优解,因为它只考虑了局部信息(到目标的估计距离),而忽略了全局信息(从起点出发的实际距离)。A*算法通过综合利用局部和全局的信息从而保证可以找到最优路径。

总结

贪心搜索算法和 A* 搜索算法是两种常用的路径规划算法,A* 搜索比贪心搜索更有可能找到最优路径. 编写 A* 搜索算法时要注意openList要选择f值最小的节点, 通常采用优先队列。