Überarbeitung des A-Star-Algorithmus der Datenstruktur und des Algorithmus

Vorwort

Der A*(A-Star)-Algorithmus ist eine effektive Direktsuchmethode zur Lösung des kürzesten Wegs in einem statischen Straßennetz und auch ein effektiver Algorithmus zur Lösung vieler Suchprobleme.

Der A*-Algorithmus gehört zum heuristischen Suchalgorithmus , der die Best-First-Suche (Best-First) mit den Vorteilen des Dijkstra-Algorithmus kombiniert und schnell den kürzesten Weg vom Startpunkt zum Endpunkt im Diagramm finden kann. Seit seiner Einführung durch Peter Hart, Nils Nilsson und Bertram Raphael im Jahr 1968 hat sich der A*-Algorithmus in vielen Bereichen zu einem der am häufigsten verwendeten Pfadplanungsalgorithmen entwickelt.

  • Die Best-First-Suche findet den Zielknoten schnell, indem sie eine heuristische Funktion verwendet, um die Priorität jedes Knotens zu bewerten.
  • Der Dijkstra-Algorithmus stellt sicher, dass der gefundene Pfad der kürzeste ist, indem er die Entfernung von jedem Knoten zum Startpunkt aufzeichnet.

Der A*-Algorithmus kombiniert diese beiden Methoden und verwendet eine heuristische Funktion, um den Abstand h ( n ) h(n) jedes Knotens zum Zielknoten auszuwertenh ( n ) und kombinieren Sie diesen Wert mit der Entfernung vom Knoten zum Startpunktg ( n ) g(n)g ( n ) werden summiert, um eine Gesamtschätzung zu erhaltenf ( n ) = g ( n ) + h ( n ) f(n) = g(n) + h(n)f ( n )=g ( n )+h ( n ) . Der A*-Algorithmus wählt immer den Knoten mit dem kleinsten geschätzten Gesamtwert für die Erweiterung aus, sodass er schnell den kürzesten Weg vom Startpunkt zum Endpunkt finden kann.

Darüber hinaus verfügt es über ein breites Anwendungsspektrum in den Bereichen Spieleentwicklung, künstliche Intelligenz und Operations Research.

1. Prinzip

1.1 Gitterabstand

Entsprechend der topologischen Struktur des Gitters können die folgenden drei Abstände verwendet werden, vorausgesetzt, dass die horizontalen und vertikalen Koordinatenabstände von A und B x bzw. y sind:

  1. Manhattan-Entfernung, es dürfen sich nur 4 Richtungen bewegen, die Entfernung von AB beträgt: x + yx + yX+j

  2. Diagonaler Abstand, der eine Bewegung in 8 Richtungen ermöglicht. Der Abstand von AB beträgt: x + y + ( 2 − 2 ) ∗ min ( x , y ) x + y + (\sqrt{2}-2)*min(x, y )X+j+(2 2 )min ( x ,y )

  3. Euklidischer Abstand, der eine Bewegung in jede Richtung ermöglicht. Der Abstand von AB beträgt: x 2 + y 2 \sqrt{x^2+y^2}X2+j2

Die folgende Standard-Manhattan-Entfernung

1.2 Breitensuche

Die Breitensuche ( Breadth First Search , kurz BFS ) wurde in meinem vorherigen Blog vorgestellt und ist der grundlegendste Algorithmus zum Finden des kürzesten Pfades. BFS entspricht einer heftigen Suche. Dieser Algorithmus weist zwei Hauptfehler bei der Suche im Raster auf:

  1. Die Kosten für den Wechsel zwischen Netzen sind gleich, und die Kosten für den Wechsel zwischen verschiedenen Netzen werden in tatsächlichen Anwendungen unterschiedlich sein.
  2. Es sucht rundherum gleichmäßig, selbst wenn Sie das Ziel rechts mit bloßem Auge sehen, wird es sich gleichmäßig verteilen.

Breitensuche 0.gif

wenn man auf Hindernisse stößt

Breitensuche 1.gif

1.3 Dijkstras Algorithmus

Der Dijkstra-Algorithmus ist ein Algorithmus, der 1959 vom niederländischen Informatiker Dijkstra vorgeschlagen wurde. Es ist der Algorithmus für den kürzesten Weg von einem Scheitelpunkt zu anderen Scheitelpunkten und löst das Problem des kürzesten Weges im gewichteten Diagramm . Das Hauptmerkmal des Dijkstra-Algorithmus besteht darin, vom Startpunkt aus zu beginnen, die Strategie des Greedy-Algorithmus zu übernehmen und zu den benachbarten Knoten des Scheitelpunkts zu wechseln, der dem Startpunkt am nächsten liegt und nicht jedes Mal besucht wurde, bis er sich erstreckt der Endpunkt.

Im Spiel „Civilization“ beispielsweise kostet das Gehen auf flachem Land oder in der Wüste 1 Bewegungspunkt, das Reisen durch Wälder oder Hügel kann jedoch 5 Bewegungspunkte kosten.

bfs_dijkstra.gif

1.4 Best-First-Suche

Der Best-First-Suchalgorithmus ist ein heuristischer Suchalgorithmus, der eine heuristische Funktion verwendet, die Knoten bevorzugt, die näher am Endpunkt liegen.

Die folgende Abbildung zeigt, dass der Dijkstra-Algorithmus bei gleichen Gewichten zwischen den Gittern zu einer Breitensuche degeneriert, die Suchzeit länger ist und das Suchgitter größer ist

dijkstra_bfs.gif

1,5 A*-Algorithmus

Die oben erwähnte Best-First-Suche ist nicht unbedingt die kürzeste. Beispielsweise wird im folgenden Szenario nicht der kürzeste Weg ausgewählt

dijkstra_vs_bfs.gif

Zu diesem Zeitpunkt kommt A * ins Spiel, das den Dijkstra-Algorithmus und den Best-First-Suchalgorithmus kombiniert. Es kombiniert die Vorteile beider und wählt die Summe zweier Werte (zurückgelegte Entfernung + geschätzte Entfernung) als aus Priorität des Knotens.

a_star_compare.png

2. Code-Implementierung

2.1 Pseudocode

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-Implementierung

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)

Wenn Sie den obigen Code verwenden, werden die vom Pfad durchlaufenen Knoten wie unten gezeigt ausgedruckt. Dies ist jedoch nicht sehr intuitiv, um den Pfad anzuzeigen. Sie müssen die Daten visualisieren

[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 Visualisierung

Rasterdaten können mit matplotlib visualisiert werden

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

Das Obige ist der Manhattan-Abstand in 4 Richtungen, und der Code kann einfach geändert werden, um den diagonalen Abstand in 8 Richtungen zu realisieren

a_star_1.png

3. Analyse der Vor- und Nachteile

3.1 Vorteile

  1. Im Vergleich zu dem Algorithmus, der alle Knoten durchsuchen muss, besteht der größte Vorteil des A*-Algorithmus darin, dass er heuristische Informationen als Entscheidungshilfe für die Bewegung zum Zielpunkt einführt, sodass nicht mehr die gesamte Karte durchquert werden muss, was den Rechenaufwand reduziert Komplexität und Zeitaufwand.

  2. Die durch den A*-Algorithmus erhaltene optimale Lösung kann theoretisch bewiesen werden. Wenn die Lösung nicht optimal ist, stimmt etwas mit der Implementierung nicht oder sie ist gemäß der Definition von A* nicht vollständig implementiert.

3.2 Nachteile

  1. Schlechte Echtzeitleistung, da die Anzahl der Knoten zunimmt und die Sucheffizienz des Algorithmus abnimmt

  2. Für den Fall, dass es keine Lösung gibt (z. B. gibt es beim Pfadfindungsproblem überhaupt keinen zugänglichen Pfad), werden mit der Verwendung des A*-Algorithmus zur Lösung zwangsläufig alle Möglichkeiten ausgeschöpft.

  3. Der A*-Algorithmus muss eine heuristische Funktion verwenden, um die Kosten unbekannter Knoten abzuschätzen. Wenn die heuristische Funktion ungenau ist, kann dies dazu führen, dass der gefundene Pfad nicht der kürzeste ist.

Referenz

  1. Einführung in den A*-Algorithmus
  2. Aus der detaillierten Erläuterung des A*-Algorithmus lässt sich der vollständige Codekommentar auf einen Blick manuell ableiten
  3. Eine einfache Implementierung des Python A*-Algorithmus
  4. A*-Algorithmus
  5. Missverständnisse des A*-Algorithmus
  6. Autonome Fahrpfadplanung: A* (Astar) Algorithmus

Je suppose que tu aimes

Origine blog.csdn.net/qq_23091073/article/details/131066745
conseillé
Classement