データ構造とアルゴリズムの A スター アルゴリズムを再考する

序文

A*(A-Star) アルゴリズムは、静的な道路ネットワーク内の最短経路を解決するための効果的な直接探索方法であり、多くの探索問題を解決するのにも効果的なアルゴリズムです。

A* アルゴリズムはヒューリスティック検索アルゴリズムに属し、最良優先 (Best-First) 検索ダイクストラ アルゴリズムの利点を組み合わせたもので、グラフ内の始点から終点までの最短経路を迅速に見つけることができます。A* アルゴリズムは、1968 年に Peter Hart、Nils Nilsson、Bertram Raphael によって提案されて以来、多くの分野で最も一般的に使用される経路計画アルゴリズムの 1 つになりました。

  • 最良優先検索では、ヒューリスティック関数を使用して各ノードの優先順位を評価することで、ターゲット ノードを迅速に見つけます。
  • ダイクストラのアルゴリズムは、各ノードから開始点までの距離を記録することにより、見つかったパスが最短であることを保証します。

A* アルゴリズムはこれら 2 つの方法を組み合わせたもので、ヒューリスティック関数を使用して各ノードからターゲット ノードまでの距離h ( n ) h(n)を評価します。h ( n ) を計算し、この値をノードから始点までの距離g ( n ) g(n)g ( n )を合計して合計推定値を取得しますf ( n ) = g ( n ) + h ( n ) f(n) = g(n) + h(n)f ( n )=g ( n )+h ( n )A* アルゴリズムは常に展開の合計推定値が最小のノードを選択するため、開始点から終了点までの最短パスを迅速に見つけることができます。

さらに、ゲーム開発、人工知能、オペレーションズリサーチの分野でも幅広い用途があります。

1. 原則

1.1 グリッド距離

グリッドの位相構造に従って、A と B の水平座標距離と垂直座標距離をそれぞれ x と y とすると、次の 3 つの距離を使用できます。

  1. マンハッタン距離、4 方向のみの移動が許可されます。AB の距離はx + yx + yです。バツ+y

  2. 8 方向の移動を可能にする対角線の距離。AB の距離は次のとおりです: x + y + ( 2 − 2 ) ∗ min ( x , y ) x + y + (\sqrt{2}-2)*min(x, y )バツ+y+(2 2 )( x ,y )

  3. 任意の方向への移動を許可するユークリッド距離。AB の距離は次のとおりです: x 2 + y 2 \sqrt{x^2+y^2}バツ2+y2

以下のデフォルトのマンハッタン距離

1.2 幅優先検索

前回のブログで紹介した幅優先探索 ( Breadth First Search 、 BFSと呼ばれます) は、最短経路を見つけるための最も基本的なアルゴリズムです。BFS は暴力的な検索と同等ですが、このアルゴリズムにはグリッド内の検索において 2 つの大きな欠陥があります。

  1. 実際のアプリケーションでは、グリッド間を移動するコストは同じですが、異なるグリッド間を移動するコストは異なります。
  2. 周囲を均等に探索し、肉眼で右側のターゲットを確認しても周囲に均等に広がります。

幅優先検索 0.gif

障害物に遭遇したとき

幅優先検索 1.gif

1.3 ダイクストラのアルゴリズム

ダイクストラアルゴリズムは、1959年にオランダのコンピュータ科学者ダイクストラによって提案されたアルゴリズムです。これは、ある頂点から他の頂点への最短経路アルゴリズムであり、重み付きグラフの最短経路問題を解決します。ダイクストラのアルゴリズムの主な特徴は、開始点から開始し、貪欲アルゴリズムの戦略を採用し、開始点に最も近く、毎回訪問されていない頂点の隣接ノードを、頂点に拡張されるまでトラバースすることです。終点。

たとえば、ゲーム「Civilization」では、平地または砂漠を歩くには 1 移動ポイントがかかりますが、森林や丘を移動する場合は 5 移動ポイントがかかる場合があります。

bfs_dijkstra.gif

1.4 最良優先検索

最良優先検索アルゴリズムは、終点に近いノードを優先するヒューリスティック関数を使用するヒューリスティック検索アルゴリズムです。

以下の図は、グリッド間の重みが等しい場合、ダイクストラ アルゴリズムが幅優先検索に変質し、検索時間が長くなり、検索グリッドがより多くなることを示しています。

dijkstra_bfs.gif

1.5A* アルゴリズム

前述の最良優先探索は必ずしも最短であるとは限りません。たとえば、次のシナリオでは最短パスは選択されません。

dijkstra_vs_bfs.gif

このとき、ダイクストラアルゴリズムと最良優先探索アルゴリズムを組み合わせたA*が登場し、両者の利点を組み合わせ、2つの値の合計(走行距離+推定距離)を選択します。ノードの優先順位。

a_star_compare.png

2. コードの実装

2.1 擬似コード

frontier = PriorityQueue()
frontier.put(start, 0)
came_from = dict()
came_from[start] = None

while not frontier.empty():
   current = frontier.get()

   if current == goal:
      break
   
   for next in graph.neighbors(current):
      if next not in came_from:
         priority = heuristic(goal, next)
         frontier.put(next, priority)
         came_from[next] = current

2.2 Pythonの実装

import numpy as np
import heapq
from collections import namedtuple
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties 
font_set = FontProperties(fname=r"c:\\windows\\fonts\\simsun.ttc", size=15)#导入宋体字体文件

# 15*15网格,0表示可以通过的点,而1表示障碍物
grid = np.array([[0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,0, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,0, 0, 0],
                 [0, 0, 1, 1, 1, 1,1, 1, 1, 1, 1, 1,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,1, 0, 0],
                 [0, 0, 1, 1, 1, 1,1, 1, 1, 1, 1, 1,1, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,0, 0, 0],
                 [0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0,0, 0, 0]])

Node = namedtuple('Node', ['x', 'y'])
dir = [Node(0, 1), Node(0, -1), Node(1, 0), Node(-1, 0)]
# dir = [Node(0, 1), Node(0, -1), Node(1, 0), Node(-1, 0),
#        Node(1, 1), Node(-1, -1), Node(1, -1), Node(-1, 1) ]
start = Node(12, 0)
end = Node(2, 14)


def distance(node1, node2):
    x = abs(node1.x - node2.x)
    y = abs(node1.y - node2.y)
    return x + y
    # return x + y + (2**0.5 - 2) * min(x, y)


def far_cost(node1, node2):
    return distance(node1, node2)


def a_star(grid, start, end):
    if grid[start.x][start.y] == 1 or grid[end.x][end.y] == 1:
        print("起/终点在障碍物上")
        return []
    rows, cols = grid.shape
    # open_set 是一个优先队列,用于存储待扩展的节点。每次从 open_set 中取出代价最小的节点进行扩展。
    open_set = []
    # cost 是一个字典,用于存储从起点到每个节点的代价。
    cost = {
    
    }
    cost[start] = 0
    # path 是一个字典,存储最终路径每个节点的前一个节点
    path = {
    
    }
    path[start] = None
    heapq.heappush(open_set, (0, start))

    while (open_set):
        current = heapq.heappop(open_set)[1]
        if (current == end):
            break
        for p in dir:
            newpx = current.x + p.x
            newpy = current.y + p.y
            if newpx < 0 or newpx >= rows or newpy < 0 or newpy >= cols or grid[newpx][newpy] == 1:
                continue
            newNode = Node(newpx, newpy)
            newCost = cost[current] + far_cost(current, newNode)
            
            if not newNode in cost or newCost < cost[newNode]:
                cost[newNode] = newCost
                priority = newCost + distance(newNode, end)
                heapq.heappush(open_set, (priority, newNode))
                path[newNode] = current
    if end not in path:
        print("未能到达结束点")
        return []
    shortest_path = []
    current = end
    while current != None:
        shortest_path.append(current)
        current = path[current]
    shortest_path.reverse()
    return shortest_path
nodes = a_star(grid, start ,end)
print(nodes)

上記のコードを使用すると、以下に示すように、パスによって渡されたノードが出力されますが、これはパスを表示するのがあまり直感的ではないため、データを視覚化する必要があります。

[Node(x=12, y=0), Node(x=11, y=0), Node(x=10, y=0), Node(x=9, y=0), Node(x=8, y=0), Node(x=7, y=0), Node(x=6, y=0), Node(x=5, y=0), Node(x=4, y=0), Node(x=3, y=0), Node(x=2, y=0), Node(x=2, y=1), Node(x=1, y=1), Node(x=1, y=2), Node(x=1, y=3), Node(x=1, y=4), Node(x=1, y=5), Node(x=1, y=6), Node(x=1, y=7), Node(x=1, y=8), Node(x=1, y=9), Node(x=1, y=10), Node(x=1, y=11), Node(x=1, y=12), Node(x=1, y=13), Node(x=1, y=14), Node(x=2, y=14)]

2.3 視覚化

グリッド データは matplotlib を使用して視覚化できます

def draw(nodes):
    fig, ax = plt.subplots()
    im = ax.imshow(grid)
    rows, cols = grid.shape
  
    ax.set_xticks(np.arange(rows))
    ax.set_yticks(np.arange(cols))
 
    plt.setp(ax.get_xticklabels(), rotation_mode="anchor")

    for n in nodes:
        text = ax.text(n.y, n.x, '2', ha="center", va="center", color="blue")
    # bgColor = ['black', 'gray']
    for i in range(rows):
        for j in range(cols):
            if Node(i, j) not in nodes:
                text = ax.text(j, i, grid[i][j],
                               ha="center", va="center", color="white")#, backgroundcolor=bgColor[grid[i][j]])
    ax.set_title('A*算法', fontproperties = font_set)
    fig.tight_layout()
    plt.show()
nodes = a_star(grid, start ,end)
draw(nodes)

a_star_0.png

上記は 4 方向のマンハッタン距離ですが、コードを変更するだけで 8 方向の対角距離を実現できます。

a_star_1.png

3. メリットとデメリットの分析

3.1 利点

  1. すべてのノードを検索する必要があるアルゴリズムと比較して、A* アルゴリズムの最大の利点は、ターゲット ポイントに移動するための意思決定支援としてヒューリスティック情報を導入するため、マップ全体を横断する必要がなくなり、計算量が削減されることです。複雑さと時間の消費。

  2. A* アルゴリズムによって得られる最適解は理論的に証明できます。ソリューションが最適でない場合は、実装に問題があるか、A* の定義に従って完全に実装されていません。

3.2 欠点

  1. リアルタイム パフォーマンスが低く、ノード数が増えるとアルゴリズムの検索効率が低下します。

  2. 解決策がない場合 (たとえば、経路探索問題では、アクセス可能な経路がまったく存在しない)、A* アルゴリズムを使用して解決すると、必然的にすべての可能性が使い果たされます。

  3. A* アルゴリズムでは、ヒューリスティック関数を使用して未知のノードのコストを推定する必要があります。ヒューリスティック関数が不正確な場合、見つかったパスが最短ではなくなる可能性があります。

参考

  1. A* アルゴリズムの概要
  2. A* アルゴリズムの詳細な説明により、手動で完全なコード コメントを一目で推測できます。
  3. Python A* アルゴリズムの簡単な実装
  4. A* アルゴリズム
  5. A* アルゴリズムの誤解
  6. 自動運転経路計画:A*(Astar)アルゴリズム

おすすめ

転載: blog.csdn.net/qq_23091073/article/details/131066745