Schreiben Sie A * (A-Stern) -Algorithmen in Python

Überblick

Was zu verwenden

Was ist der A \ * -Algorithmus?

Dieser Algorithmus verwendet die Summe von g als Entfernung zur aktuellen Zeit und den geschätzten Wert h zum Ziel als Parameter während der Suche und berechnet die kürzeste Entfernung der Route bei relativ geringen Suchkosten. Es wird oft in Spielen verwendet.

Es gibt Fälle, in denen die kürzeste Entfernung nicht berechnet wird, während die Suchkosten niedrig sind. Tiefensuche (DFS) oder Breitensuche (BFS), die die kürzeste Entfernung berechnen kann, jedoch tendenziell hohe Berechnungskosten verursacht. ) Neigt dazu, bessere Ergebnisse zu erzielen.

Es basierte ursprünglich auf einem 1968 veröffentlichten Artikel. Die Gruppe von Algorithmen, die die kürzeste Entfernung finden, wird in diesem Artikel als "akzeptabel - zulässig" beschrieben. Das führende A ist also die Menge der Algorithmen, und diejenige, die die Anzahl der Auswertungen für die Berechnung der kürzesten Route optimiert, ist A \ *. Die Notation ist der Ursprung des Namens des Algorithmus.

Artikel-Link: Eine formale Grundlage für die heuristische Bestimmung von Mindestkostenpfaden

Berechnungsinhalt

Die Gesamtkosten für das Verschieben einer Position (Knoten / Knoten) nach n betragen $ f (n) $, die Kosten von der Startposition zum Knoten an Position n betragen $ g (n) $ und ein als heuristisch bezeichneter Wert. Unter der Annahme, dass die berechnete Funktion (geschätzter Wert der Entfernung zum Ziel) $ h (n) $ ist, wird $ f (n) $ durch die folgende Summe berechnet.

f(n) = g(n) + h(n)

Obwohl nicht streng, berechnet dieser Artikel einfach $ g (n) $ als die Anzahl der Knoten, die von Anfang an verschoben wurden (einfach 1 für jede Bewegung), um die Berechnung zu vereinfachen. Entsprechend in Form von Addition).

Außerdem wird die Berechnung von $ h (n) $ dieses Mal gelöst. Da das Labyrinth ein Gittertyp ist, der nach oben, unten, links und rechts verschoben werden kann, wird die Manhattan-Distanz verwendet, wobei eine Bewegung nach oben, unten, links und rechts die Distanz 1 ist.

Implementierung in Python

Ich werde den Code zum Verständnis in Python schreiben.

Korrespondenz von Code, um ein Labyrinth aus Gittern zu erstellen

Verwenden Sie zum Erstellen eines Labyrinthgitters das aus "Suche nach Tiefenpriorität in Python schreiben". Die Details der Erklärung sind im Link geschrieben, daher werde ich sie weglassen, aber die grobe Zusammenfassung lautet wie folgt.

from __future__ import annotations
from typing import Optional
from typing import List
import random

#Konstanter Wert für den Eingang.
CELL_TYPE_START: str = 'S'

#Konstanter Wert für Passagen.
CELL_TYPE_PASSAGE: str = ' '

#Der konstante Wert der Wand.
CELL_TYPE_WALL: str = 'W'

#Konstanten Wert verlassen.
CELL_TYPE_GOAL: str = 'G'

#Ein konstanter Wert für den berechneten Routenpfad.
CELL_TYPE_PATH: str = '*'


class Location:

    def __init__(self, row: int, column: int) -> None:
        """
Eine Klasse, die eine einzelne Positionsinformation des Labyrinthgitters verarbeitet.

        Parameters
        ----------
        row : int
Die Zeilennummer der Position. Beginnen Sie bei 0, 1 von oben nach unten
Wird hinzugefügt werden.
        column : int
Die Spaltennummer der Position. Beginnen Sie bei 0, 1 von links nach rechts
Wird hinzugefügt werden.
        """
        self.row: int = row
        self.column: int = column


class Maze:

    #Die vertikale Anzahl der zu erzeugenden Labyrinthgitter.
    _ROW_NUM: int = 7

    #Die Nummer neben dem Labyrinthgitter, die generiert werden soll.
    _COLUMN_NUM: int = 15

    #Prozentsatz der zu erzeugenden Wände. 1.Je näher es an 0 liegt, desto mehr Wände gibt es.
    _WALL_SPARSENESS: float = 0.3

    def __init__(self) -> None:
        """
Eine Klasse, die die Erzeugung und Steuerung eines zufälligen Labyrinthgitters übernimmt.

        Notes
        -----
Da jeder Zelltyp zufällig festgelegt wird, ist dies nicht immer von Anfang an der Fall
Beachten Sie, dass Sie nichts tun können, um Ihr Ziel zu erreichen.
        """

        self._set_start_and_goal_location()
        self._grid: List[List[str]] = []
        self._fill_grid_by_passage_cell()
        self._set_wall_type_to_cells_randomly()
        self._set_start_and_goal_type_to_cell()

    def _set_start_and_goal_location(self) -> None:
        """
Stellen Sie die Attribute der Koordinaten des Startpunkts (Eingang) und des Ziels (Ausgang) ein.
        """
        self.start_loc: Location = Location(row=0, column=0)
        self.goal_loc: Location = Location(
            row=self._ROW_NUM - 1,
            column=self._COLUMN_NUM - 1)

    def _fill_grid_by_passage_cell(self) -> None:
        """
Fügen Sie allen Zellen Zellen hinzu und legen Sie den Zelltyp der Passage fest.
        """
        for row in range(self._ROW_NUM):
            row_cells: List[str] = []
            for column in range(self._COLUMN_NUM):
                row_cells.append(CELL_TYPE_PASSAGE)
            self._grid.append(row_cells)

    def _set_wall_type_to_cells_randomly(self) -> None:
        """
Legen Sie den Wandzelltyp für jede Zelle im Raster zufällig fest.
        """
        for row in range(self._ROW_NUM):
            for column in range(self._COLUMN_NUM):
                probability = random.uniform(0.0, 1.0)
                if probability >= self._WALL_SPARSENESS:
                    continue
                self._grid[row][column] = CELL_TYPE_WALL

    def _set_start_and_goal_type_to_cell(self) -> None:
        """
An der Startposition (Eingang) bzw. an der Zielposition (Ausgang)
Stellen Sie den Zelltyp ein.
        """
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def is_goal_loc(self, location: Location) -> bool:
        """
Ruft den booleschen Wert ab, ob die angegebene Position die Zielposition ist.

        Parameters
        ----------
        location : Location
Position für das Urteil.

        Returns
        -------
        result : bool
True wird gesetzt, wenn es sich um die Zielposition handelt.
        """
        if (location.row == self.goal_loc.row
                and location.column == self.goal_loc.column):
            return True
        return False

    def get_movable_locations(self, location: Location) -> List[Location]:
        """
Holen Sie sich eine Liste der beweglichen Positionen von der angegebenen Position.

        Parameters
        ----------
        location : Location
Eine Instanz der Referenzposition.

        Returns
        -------
        movable_locations : list of Location
Eine Liste mit Instanzen von beweglichen Orten.
        """
        movable_locations: List[Location] = []

        #Beurteilung der Verarbeitung, ob es nach oben verschoben werden kann oder nicht.
        if location.row + 1 < self._ROW_NUM:
            is_wall: bool = self._grid[location.row + 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row + 1, column=location.column))

        #Beurteilungsverarbeitung, ob es nach unten verschoben werden kann.
        if location.row - 1 >= 0:
            is_wall = self._grid[location.row - 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row - 1, column=location.column))

        #Urteilsverarbeitung, ob es nach rechts verschoben werden kann.
        if location.column + 1 < self._COLUMN_NUM:
            is_wall = self._grid[location.row][location.column + 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column + 1))

        #Beurteilungsverarbeitung, ob es nach links verschoben werden kann.
        if location.column - 1 >= 0:
            is_wall = self._grid[location.row][location.column - 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column - 1))

        return movable_locations

    def set_path_type_to_cells(self, path: List[Location]) -> None:
        """
Für Zellen, die im angegebenen Pfad zum Ein- und Ausgang enthalten sind
Legen Sie den Zelltyp des Pfads fest.

        Parameters
        ----------
        path : list of Location
Die Positionsinformationen jeder Zelle vom Eingang bis zum Ausgang, die durch die Suche erhalten wurden
Gespeicherte Liste.
        """
        for location in path:
            self._grid[location.row][location.column] = CELL_TYPE_PATH

        #Die im Pfad enthaltenen Eingangs- und Ausgangsteile sind das Original
        #Reflektieren Sie den Zelltyp.
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def __str__(self) -> str:
        """
Ruft die Typzeichenfolge für jede Zelle im Raster ab.

        Returns
        -------
        grid_str : str
Eine Zeichenfolge für jede Zelle im Raster.
        """
        grid_str: str = ''
        for row_cells in self._grid:
            grid_str += '-' * self._COLUMN_NUM * 2
            grid_str += '\n'
            for cell_type in row_cells:
                grid_str += cell_type
                grid_str += '|'
            grid_str += '\n'
        return grid_str


class Node:

    def __init__(self, location: Location, parent: Optional[Node]):
        """
Daten für einen einzelnen Knoten, um Informationen wie die Position und den Übergang des Labyrinths aufzunehmen
Klasse zu handhaben.

        Parameters
        ----------
        location : Location
Eine Instanz, die die Standortinformationen des Ziels verarbeitet.
        parent : Node or None
Eine Instanz eines Knotens, der Standortinformationen vor dem Verschieben verarbeitet. Zu Beginn der Suche
Usw. wird None sein.
        """
        self.location: Location = location
        self.parent: Optional[Node] = parent


def get_path_from_goal_node(goal_node: Node) -> List[Location]:
    """
Vom Ausgangsknoten der Pfad vom Eingang, der durch Suchen zum Ausgang erhalten wird
erhalten.

    Parameters
    ----------
    goal_node : Node
Eine Instanz des Ziel-Exit- (Ziel-) Knotens.

    Returns
    -------
    path : list of Location
Eine Liste mit Instanzen jedes Standorts vom Eingang bis zum Ausgang.
    """
    path: List[Location] = [goal_node.location]
    node: Node = goal_node
    while node.parent is not None:
        node = node.parent
        path.append(node.location)
    path.reverse()
    return path

Wenn Sie versuchen, das als Versuch erzeugte Labyrinth auszugeben, wird es wie unten gezeigt in einem Raster angezeigt.

if __name__ == '__main__':
    maze = Maze()
    print(maze)
------------------------------
S| |W| |W| | | |W| |W| |W| |W|
------------------------------
W| | | | | |W| | | |W|W| |W|W|
------------------------------
W| | | | |W| | | |W|W| | | | |
------------------------------
 | |W| |W|W| | | | | | | |W|W|
------------------------------
W|W|W| | | |W| | | | | | | |W|
------------------------------
W|W| | | | | | | |W| |W| | | |
------------------------------
W|W| | | | |W| | | | | | | |G|

Erstellen von Prioritätswarteschlangen und Anpassen von Knotenklassen

Der A \ * -Algorithmus verwendet eine Prioritätswarteschlange als Datenstruktur. Dies ist eine spezielle Warteschlange, in der Werte mit Priorität zur Warteschlange hinzugefügt werden. Der Wert mit der höchsten Priorität wird auch beim Abrufen des Werts berücksichtigt.

Sie müssen der Knotenklasse die Methode __lt__ hinzufügen, da der Prioritätswert vom Operator kleiner als der Vergleich (<) für die Instanz des Knotens berechnet wird, den Sie der Warteschlange hinzufügen möchten.

Bei der Methode lt wird $ g (n) + h (n) $ in der Kostenformel berechnet und mit einem anderen Knoten verglichen.

Passen Sie außerdem den Wert von $ g (n) $ und den Wert von $ h (n) $ so an, dass sie in den Klassenattributen enthalten sind, da sie für die Berechnung erforderlich sind.

class Node:

    def __init__(
            self, location: Location, parent: Optional[Node],
            cost: float, heuristic:float) -> None:
        """
Daten für einen einzelnen Knoten, um Informationen wie die Position und den Übergang des Labyrinths aufzunehmen
Klasse zu handhaben.

        Parameters
        ----------
        location : Location
Eine Instanz, die die Standortinformationen des Ziels verarbeitet.
        parent : Node or None
Eine Instanz eines Knotens, der Standortinformationen vor dem Verschieben verarbeitet. Zu Beginn der Suche
Usw. wird None sein.
        cost : float
Kostenwert von der Startposition bis zur Position des entsprechenden Knotens (g(n)damit
Erhaltener Wert).
        heuristic : float
Geschätzte Entfernung von diesem Knoten zum Ausgang (h(n)Wert erhalten durch).
        """
        self.location: Location = location
        self.parent: Optional[Node] = parent
        self.cost = cost
        self.heuristic = heuristic

    def __lt__(self, other_node: Node) -> bool:
        """
Vergleichsoperator( < )Methode zur Verarbeitung durch.
Wird zur Steuerung priorisierter Warteschlangen verwendet.

        Parameters
        ----------
        other_node : Node
Instanzen anderer zu vergleichender Knoten.

        Returns
        -------
        result_bool : bool
Vergleichsergebnis. Berechnungsverarbeitung sind die Kosten vom Eingang (g(n))Wann
Hulistischer Wert (h(n)) Durch Vergleichen des Gesamtwertes
Getan werden.
        """
        left_value: float = self.cost + self.heuristic
        right_value: float = other_node.cost + other_node.heuristic
        result_bool: bool = left_value < right_value
        return result_bool

Ich habe die Methode "lt" noch nie alleine verwendet, daher werde ich versuchen, mehrere Instanzen zu erstellen und sie zu vergleichen, um die Ergebnisse anzuzeigen. Der Vergleich basiert auf der Summe aus Kosten und Heuristik, sodass der erste Vergleich falsch und der zweite wahr ist.

if __name__ == '__main__':
    node_1 = Node(
        location=Location(row=0, column=0),
        parent=None,
        cost=2, heuristic=2)
    node_2 = Node(
        location=Location(row=0, column=0),
        parent=None,
        cost=1, heuristic=2)
    node_3 = Node(
        location=Location(row=0, column=0),
        parent=None,
        cost=3, heuristic=2)
    print(node_1 < node_2)
    print(node_1 < node_3)
False
True

Als nächstes bereiten wir die Prioritätswarteschlange vor. Die Verarbeitung der Prioritätswarteschlange kann mithilfe des heapq-Pakets des Python-Standardmoduls erfolgen.

import heapq
...


class PriorityQueue:

    def __init__(self) -> None:
        """
Eine Klasse zum Steuern von Prioritätswarteschlangen.
        """
        self._container: List[Node] = []

    @property
    def empty(self) -> bool:
        """
Attributwert, ob die Warteschlange leer ist.

        Returns
        -------
        result : bool
Auf Leer setzen, wenn leer.
        """
        return not self._container

    def push(self, node: Node) -> None:
        """
Fügen Sie der Warteschlange eine Instanz des Knotens hinzu.

        Parameters
        ----------
        node : Node
Instanz des hinzuzufügenden Knotens.
        """
        heapq.heappush(self._container, node)

    def pop(self) -> Node:
        """
Extrahieren Sie die Instanz des Knotens mit der höchsten Priorität aus der Warteschlange.

        Returns
        -------
        node : Node
Eine Instanz der abgerufenen Node-Klasse.
        """
        return heapq.heappop(self._container)

Lassen Sie uns tatsächlich ein wenig bewegen und versuchen, ob die Verarbeitung auf eine Weise erfolgt, die Priorität berücksichtigt. Es ist zu beachten, dass das Formular "Niedrige Kosten sind vorzuziehen (= hohe Priorität)". Es wird nicht von Pop aus dem mit dem höchsten Gesamtkostenwert herausgenommen.

if __name__ == '__main__':
    priority_queue = PriorityQueue()
    priority_queue.push(
        node=Node(
            location=Location(row=0, column=0),
            parent=None,
            cost=1,
            heuristic=1))
    priority_queue.push(
        node=Node(
            location=Location(row=0, column=0),
            parent=None,
            cost=3,
            heuristic=3))
    priority_queue.push(
        node=Node(
            location=Location(row=0, column=0),
            parent=None,
            cost=2,
            heuristic=3))

    node: Node = priority_queue.pop()
    print(node.cost, node.heuristic)
    node = priority_queue.pop()
    print(node.cost, node.heuristic)
    node = priority_queue.pop()
    print(node.cost, node.heuristic)

Sie können sehen, dass die Ausgabeergebnisse in aufsteigender Reihenfolge der Gesamtkosten abgerufen werden.

1 1
2 3
3 3

Hinzufügen einer Verarbeitung zum Erfassen der Manhattan-Entfernung zwischen der Zielposition und der Ausgangsposition (Zielposition)

Da diesmal die Berechnung der Heuristik ($ h (n) $) ein Labyrinth im Rasterformat ist, wird die Manhattan-Entfernung verwendet. Fügen Sie diese Funktion daher der Labyrinth-Klasse hinzu.

    def get_manhattan_distance(self, location: Location) -> int:
        """
Der Manhattan-Abstand zwischen der Zielposition und der Ausgangsposition (Zielposition)
erhalten.

        Parameters
        ----------
        location : Location
Eine Instanz des Zielorts.

        Returns
        -------
        distance : int
Manhattan Abstand zwischen der Zielposition und der Ausgangsposition. Spaltenrichtung
Die Summe aus dem Absolutwert der Differenz und dem Absolutwert der Differenz in Zeilenrichtung wird eingestellt.
        """
        x_distance: int = abs(location.column - self.goal_loc.column)
        y_distance: int = abs(location.row - self.goal_loc.column)
        distance: int = x_distance + y_distance
        return distance

A \ * Hinzufügung einer Algorithmusfunktion

Nachdem wir alle notwendigen Vorbereitungen getroffen haben, erstellen wir eine Funktion für den A \ * -Algorithmus. Der Funktionsname ist astar.

from typing import Callable, Dict
...

def astar(
        init_loc: Location,
        is_goal_loc_method: Callable[[Location], bool],
        get_movable_locations_method: Callable[[Location], List[Location]],
        hueristic_method: Callable[[Location], int],
        ) -> Optional[Node]:
    """
    A*Führen Sie die Suchverarbeitung nach Algorithmus durch.

    Parameters
    ----------
    init_loc : Location
Suche Startposition (Position des Eingangs des Labyrinths).
    is_goal_loc_method : callable
Eine Methode, die bestimmt, ob die Zielposition der Ausgang (Ziel) ist.
    get_movable_locations_method : callable
Eine Methode zum Abrufen einer Liste von Zellenpositionen, die von der Zielposition verschoben werden sollen.
    hueristic_method : callable
Ermitteln Sie den Abstand zwischen der Zielposition und der Ausgangsposition (Zielposition)
Eine Methode zur Heuristik für.

    Returns
    -------
    goal_node : Node or None
Eine Instanz des Knotens am berechneten Austrittsort. Zum Ausgang
Keine wird in Fällen festgelegt, in denen die Route nicht berechnet werden kann.
    """
    frontier_queue: PriorityQueue = PriorityQueue()
    frontier_queue.push(
        node=Node(
            location=init_loc,
            parent=None,
            cost=0,
            heuristic=hueristic_method(init_loc)))

    explored_loc_cost_dict: Dict[Location, float] = {init_loc: 0.0}

    while not frontier_queue.empty:
        current_node: Node = frontier_queue.pop()
        current_loc: Location = current_node.location

        if is_goal_loc_method(current_loc):
            return current_node

        movable_locations = get_movable_locations_method(current_loc)
        for movable_location in movable_locations:
            new_cost: float = current_node.cost + 1

            #Ein neues Ziel wurde bereits gesucht und ist nicht kostengünstig
            #Wenn ja, überspringen Sie es.
            if (movable_location in explored_loc_cost_dict and
                    explored_loc_cost_dict[movable_location] <= new_cost):
                continue

            explored_loc_cost_dict[movable_location] = new_cost
            frontier_queue.push(
                node=Node(
                    location=movable_location,
                    parent=current_node,
                    cost=new_cost,
                    heuristic=hueristic_method(movable_location)))
    return None

Ich habe früher geschrieben: "Schreibe Suche mit Tiefenpriorität in Python. “ähnelt der Ablauf der dfs-Funktion im Artikel. Da die Verarbeitung teilweise unterschiedlich ist, werde ich hauptsächlich den Unterschied erklären.

Zunächst wird für das Argument das Callable (hueristic_method) für die Heuristik hinzugefügt. Dies gibt die Methode zur Berechnung der Manhattan-Entfernung an, die vor einiger Zeit in diesem Beispiel hinzugefügt wurde.

    frontier_queue: PriorityQueue = PriorityQueue()
    frontier_queue.push(
        node=Node(
            location=init_loc,
            parent=None,
            cost=0,
            heuristic=hueristic_method(init_loc)))

Außerdem wurde die Warteschlange durch die der Prioritätswarteschlange (frontier_queue) ersetzt. Wir fügen dieser Warteschlange Knoten hinzu, die untersucht werden sollen.

    explored_loc_cost_dict: Dict[Location, float] = {init_loc: 0.0}

Das Wörterbuch, das die gesuchten Knoten enthält, besteht aus der Position der Position im Schlüssel und dem Wert der Kosten ($ g (n) $) im Wert. Da es sich um eine einfache Methode handelt, die bei jeder Bewegung um 1 erhöht wird, setzen Sie zuerst 0.0.

    while not frontier_queue.empty:
        current_node: Node = frontier_queue.pop()
        current_loc: Location = current_node.location

Der Vorgang wird in einer while-Schleife wiederholt, bis der zu durchsuchende Knoten aus der Warteschlange verschwindet.

        if is_goal_loc_method(current_loc):
            return current_node

Wenn der Zielknoten ein Exit (Ziel) ist, wird der Vorgang durch Zurückgeben des Exit-Knotens abgeschlossen.

        movable_locations = get_movable_locations_method(current_loc)
        for movable_location in movable_locations:
            new_cost: float = current_node.cost + 1

Jede Position, die vom aktuellen Suchzielknoten verschoben werden soll, wird von einer for-Schleife verarbeitet. Dieses Mal wird es in einer einfachen Form implementiert, die die Kosten ($ g (n) $) bei jeder Bewegung um 1 erhöht.

            #Ein neues Ziel wurde bereits gesucht und ist nicht kostengünstig
            #Wenn ja, überspringen Sie es.
            if (movable_location in explored_loc_cost_dict and
                    explored_loc_cost_dict[movable_location] <= new_cost):
                continue

Auch wenn es sich um eine gesuchte Position handelt, lohnt es sich zu suchen, ob sie kostengünstig ist. Wenn es bereits durchsucht wurde und sich die Position in Bezug auf die Kosten nicht ändert oder verschlechtert, ist die Suche nicht sinnvoll. Überspringen Sie sie daher.

            explored_loc_cost_dict[movable_location] = new_cost
            frontier_queue.push(
                node=Node(
                    location=movable_location,
                    parent=current_node,
                    cost=new_cost,
                    heuristic=hueristic_method(movable_location)))

Wenn die Bedingungen eine Suche wert sind, legen wir Werte wie Kosten im durchsuchten Wörterbuch fest und fügen Knoten zu der zu durchsuchenden priorisierten Warteschlange hinzu.

Da die Elemente mit der höchsten Priorität zuerst verarbeitet werden, können Sie eine Suche mit hervorragenden Suchkosten durchführen.

    return None

Abhängig vom erzeugten Labyrinth gibt es möglicherweise keine Route zum Ausgang. In diesem Fall wird keine zurückgegeben.

Ich werde es versuchen.

if __name__ == '__main__':
    maze: Maze = Maze()
    print(maze)

    goal_node: Optional[Node] = astar(
        init_loc=maze.start_loc,
        is_goal_loc_method=maze.is_goal_loc,
        get_movable_locations_method=maze.get_movable_locations,
        hueristic_method=maze.get_manhattan_distance,
    )
    if goal_node is None:
        print('Es ist ein Labyrinth, in dem der Ausgang nicht berechnet werden kann.')
    else:
        print('-' * 20)
        path: List[Location] = get_path_from_goal_node(
            goal_node=goal_node)
        maze.set_path_type_to_cells(path=path)
        print('Berechneter Pfad:')
        print(maze)
Berechneter Pfad:
------------------------------
S|*|*|*|*| | | |W| | | |W|W|W|
------------------------------
W| | |W|*|*|W|W|W| | | | |W| |
------------------------------
W|W| |W|W|*|W| | | | |W|W|W|W|
------------------------------
W|W|W| | |*|*|W| |W| | | | | |
------------------------------
 |W|W|W| | |*|W| | | | | |W| |
------------------------------
 | | | | |W|*|*|*|*|*| | |W| |
------------------------------
 |W| | |W|W|W| |W| |*|*|*|*|G|

S im Raster ist der Eingang (Start), G ist der Ausgang (Ziel) und der Stern ist der durch die Suche berechnete Pfad. Der Weg vom Eingang zum Ausgang wurde sicher berechnet.

Es ist ein Merkmal von A \ *, dass ein diagonaler Pfad im Vergleich zur Suche mit Breitenpriorität erhalten wird, bei der das Suchergebnis leicht rechtwinklig ist. Sogar das Beispiel, das ich dieses Mal tatsächlich ausprobiert habe, hat einen leicht diagonalen Pfad.

Ganzer Code

from __future__ import annotations
from typing import Optional
from typing import List
import random
import heapq
from typing import Callable, Dict

#Konstanter Wert für den Eingang.
CELL_TYPE_START: str = 'S'

#Konstanter Wert für Passagen.
CELL_TYPE_PASSAGE: str = ' '

#Der konstante Wert der Wand.
CELL_TYPE_WALL: str = 'W'

#Konstanten Wert verlassen.
CELL_TYPE_GOAL: str = 'G'

#Ein konstanter Wert für den berechneten Routenpfad.
CELL_TYPE_PATH: str = '*'


class Location:

    def __init__(self, row: int, column: int) -> None:
        """
Eine Klasse, die eine einzelne Positionsinformation des Labyrinthgitters verarbeitet.

        Parameters
        ----------
        row : int
Die Zeilennummer der Position. Beginnen Sie bei 0, 1 von oben nach unten
Wird hinzugefügt werden.
        column : int
Die Spaltennummer der Position. Beginnen Sie bei 0, 1 von links nach rechts
Wird hinzugefügt werden.
        """
        self.row: int = row
        self.column: int = column


class Maze:

    #Die vertikale Anzahl der zu erzeugenden Labyrinthgitter.
    _ROW_NUM: int = 7

    #Die Nummer neben dem Labyrinthgitter, die generiert werden soll.
    _COLUMN_NUM: int = 15

    #Prozentsatz der zu erzeugenden Wände. 1.Je näher es an 0 liegt, desto mehr Wände gibt es.
    _WALL_SPARSENESS: float = 0.3

    def __init__(self) -> None:
        """
Eine Klasse, die die Erzeugung und Steuerung eines zufälligen Labyrinthgitters übernimmt.

        Notes
        -----
Da jeder Zelltyp zufällig festgelegt wird, ist dies nicht immer von Anfang an der Fall
Beachten Sie, dass Sie nichts tun können, um Ihr Ziel zu erreichen.
        """

        self._set_start_and_goal_location()
        self._grid: List[List[str]] = []
        self._fill_grid_by_passage_cell()
        self._set_wall_type_to_cells_randomly()
        self._set_start_and_goal_type_to_cell()

    def _set_start_and_goal_location(self) -> None:
        """
Stellen Sie die Attribute der Koordinaten des Startpunkts (Eingang) und des Ziels (Ausgang) ein.
        """
        self.start_loc: Location = Location(row=0, column=0)
        self.goal_loc: Location = Location(
            row=self._ROW_NUM - 1,
            column=self._COLUMN_NUM - 1)

    def _fill_grid_by_passage_cell(self) -> None:
        """
Fügen Sie allen Zellen Zellen hinzu und legen Sie den Zelltyp der Passage fest.
        """
        for row in range(self._ROW_NUM):
            row_cells: List[str] = []
            for column in range(self._COLUMN_NUM):
                row_cells.append(CELL_TYPE_PASSAGE)
            self._grid.append(row_cells)

    def _set_wall_type_to_cells_randomly(self) -> None:
        """
Legen Sie den Wandzelltyp für jede Zelle im Raster zufällig fest.
        """
        for row in range(self._ROW_NUM):
            for column in range(self._COLUMN_NUM):
                probability = random.uniform(0.0, 1.0)
                if probability >= self._WALL_SPARSENESS:
                    continue
                self._grid[row][column] = CELL_TYPE_WALL

    def _set_start_and_goal_type_to_cell(self) -> None:
        """
An der Startposition (Eingang) bzw. an der Zielposition (Ausgang)
Stellen Sie den Zelltyp ein.
        """
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def is_goal_loc(self, location: Location) -> bool:
        """
Ruft den booleschen Wert ab, ob die angegebene Position die Zielposition ist.

        Parameters
        ----------
        location : Location
Position für das Urteil.

        Returns
        -------
        result : bool
True wird gesetzt, wenn es sich um die Zielposition handelt.
        """
        if (location.row == self.goal_loc.row
                and location.column == self.goal_loc.column):
            return True
        return False

    def get_movable_locations(self, location: Location) -> List[Location]:
        """
Holen Sie sich eine Liste der beweglichen Positionen von der angegebenen Position.

        Parameters
        ----------
        location : Location
Eine Instanz der Referenzposition.

        Returns
        -------
        movable_locations : list of Location
Eine Liste mit Instanzen von beweglichen Orten.
        """
        movable_locations: List[Location] = []

        #Beurteilung der Verarbeitung, ob es nach oben verschoben werden kann oder nicht.
        if location.row + 1 < self._ROW_NUM:
            is_wall: bool = self._grid[location.row + 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row + 1, column=location.column))

        #Beurteilungsverarbeitung, ob es nach unten verschoben werden kann.
        if location.row - 1 >= 0:
            is_wall = self._grid[location.row - 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row - 1, column=location.column))

        #Urteilsverarbeitung, ob es nach rechts verschoben werden kann.
        if location.column + 1 < self._COLUMN_NUM:
            is_wall = self._grid[location.row][location.column + 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column + 1))

        #Beurteilungsverarbeitung, ob es nach links verschoben werden kann.
        if location.column - 1 >= 0:
            is_wall = self._grid[location.row][location.column - 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column - 1))

        return movable_locations

    def set_path_type_to_cells(self, path: List[Location]) -> None:
        """
Für Zellen, die im angegebenen Pfad zum Ein- und Ausgang enthalten sind
Legen Sie den Zelltyp des Pfads fest.

        Parameters
        ----------
        path : list of Location
Die Positionsinformationen jeder Zelle vom Eingang bis zum Ausgang, die durch die Suche erhalten wurden
Gespeicherte Liste.
        """
        for location in path:
            self._grid[location.row][location.column] = CELL_TYPE_PATH

        #Die im Pfad enthaltenen Eingangs- und Ausgangsteile sind das Original
        #Reflektieren Sie den Zelltyp.
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def get_manhattan_distance(self, location: Location) -> int:
        """
Der Manhattan-Abstand zwischen der Zielposition und der Ausgangsposition (Zielposition)
erhalten.

        Parameters
        ----------
        location : Location
Eine Instanz des Zielorts.

        Returns
        -------
        distance : int
Manhattan Abstand zwischen der Zielposition und der Ausgangsposition. Spaltenrichtung
Die Summe aus dem Absolutwert der Differenz und dem Absolutwert der Differenz in Zeilenrichtung wird eingestellt.
        """
        x_distance: int = abs(location.column - self.goal_loc.column)
        y_distance: int = abs(location.row - self.goal_loc.column)
        distance: int = x_distance + y_distance
        return distance

    def __str__(self) -> str:
        """
Ruft die Typzeichenfolge für jede Zelle im Raster ab.

        Returns
        -------
        grid_str : str
Eine Zeichenfolge für jede Zelle im Raster.
        """
        grid_str: str = ''
        for row_cells in self._grid:
            grid_str += '-' * self._COLUMN_NUM * 2
            grid_str += '\n'
            for cell_type in row_cells:
                grid_str += cell_type
                grid_str += '|'
            grid_str += '\n'
        return grid_str


class Node:

    def __init__(
            self, location: Location, parent: Optional[Node],
            cost: float, heuristic:float) -> None:
        """
Daten für einen einzelnen Knoten, um Informationen wie die Position und den Übergang des Labyrinths aufzunehmen
Klasse zu handhaben.

        Parameters
        ----------
        location : Location
Eine Instanz, die die Standortinformationen des Ziels verarbeitet.
        parent : Node or None
Eine Instanz eines Knotens, der Standortinformationen vor dem Verschieben verarbeitet. Zu Beginn der Suche
Usw. wird None sein.
        cost : float
Kostenwert von der Startposition bis zur Position des entsprechenden Knotens (g(n)damit
Erhaltener Wert).
        heuristic : float
Geschätzte Entfernung von diesem Knoten zum Ausgang (h(n)Wert erhalten durch).
        """
        self.location: Location = location
        self.parent: Optional[Node] = parent
        self.cost = cost
        self.heuristic = heuristic

    def __lt__(self, other_node: Node) -> bool:
        """
Vergleichsoperator( < )Methode zur Verarbeitung durch.
Wird zur Steuerung priorisierter Warteschlangen verwendet.

        Parameters
        ----------
        other_node : Node
Instanzen anderer zu vergleichender Knoten.

        Returns
        -------
        result_bool : bool
Vergleichsergebnis. Berechnungsverarbeitung sind die Kosten vom Eingang (g(n))Wann
Hulistischer Wert (h(n)) Durch Vergleichen des Gesamtwertes
Getan werden.
        """
        left_value: float = self.cost + self.heuristic
        right_value: float = other_node.cost + other_node.heuristic
        result_bool: bool = left_value < right_value
        return result_bool


def get_path_from_goal_node(goal_node: Node) -> List[Location]:
    """
Vom Ausgangsknoten der Pfad vom Eingang, der durch Suchen zum Ausgang erhalten wird
erhalten.

    Parameters
    ----------
    goal_node : Node
Eine Instanz des Ziel-Exit- (Ziel-) Knotens.

    Returns
    -------
    path : list of Location
Eine Liste mit Instanzen jedes Standorts vom Eingang bis zum Ausgang.
    """
    path: List[Location] = [goal_node.location]
    node: Node = goal_node
    while node.parent is not None:
        node = node.parent
        path.append(node.location)
    path.reverse()
    return path


class PriorityQueue:

    def __init__(self) -> None:
        """
Eine Klasse zum Steuern von Prioritätswarteschlangen.
        """
        self._container: List[Node] = []

    @property
    def empty(self) -> bool:
        """
Attributwert, ob die Warteschlange leer ist.

        Returns
        -------
        result : bool
Auf Leer setzen, wenn leer.
        """
        return not self._container

    def push(self, node: Node) -> None:
        """
Fügen Sie der Warteschlange eine Instanz des Knotens hinzu.

        Parameters
        ----------
        node : Node
Instanz des hinzuzufügenden Knotens.
        """
        heapq.heappush(self._container, node)

    def pop(self) -> Node:
        """
Extrahieren Sie die Instanz des Knotens mit der höchsten Priorität aus der Warteschlange.

        Returns
        -------
        node : Node
Eine Instanz der abgerufenen Node-Klasse.
        """
        return heapq.heappop(self._container)


def astar(
        init_loc: Location,
        is_goal_loc_method: Callable[[Location], bool],
        get_movable_locations_method: Callable[[Location], List[Location]],
        hueristic_method: Callable[[Location], int],
        ) -> Optional[Node]:
    """
    A*Führen Sie die Suchverarbeitung nach Algorithmus durch.

    Parameters
    ----------
    init_loc : Location
Suche Startposition (Position des Eingangs des Labyrinths).
    is_goal_loc_method : callable
Eine Methode, die bestimmt, ob die Zielposition der Ausgang (Ziel) ist.
    get_movable_locations_method : callable
Eine Methode zum Abrufen einer Liste von Zellenpositionen, die von der Zielposition verschoben werden sollen.
    hueristic_method : callable
Ermitteln Sie den Abstand zwischen der Zielposition und der Ausgangsposition (Zielposition)
Eine Methode zur Heuristik für.

    Returns
    -------
    goal_node : Node or None
Eine Instanz des Knotens am berechneten Austrittsort. Zum Ausgang
Keine wird in Fällen festgelegt, in denen die Route nicht berechnet werden kann.
    """
    frontier_queue: PriorityQueue = PriorityQueue()
    frontier_queue.push(
        node=Node(
            location=init_loc,
            parent=None,
            cost=0,
            heuristic=hueristic_method(init_loc)))

    explored_loc_cost_dict: Dict[Location, float] = {init_loc: 0.0}

    while not frontier_queue.empty:
        current_node: Node = frontier_queue.pop()
        current_loc: Location = current_node.location

        if is_goal_loc_method(current_loc):
            return current_node

        movable_locations = get_movable_locations_method(current_loc)
        for movable_location in movable_locations:
            new_cost: float = current_node.cost + 1

            #Ein neues Ziel wurde bereits gesucht und ist nicht kostengünstig
            #Wenn ja, überspringen Sie es.
            if (movable_location in explored_loc_cost_dict and
                    explored_loc_cost_dict[movable_location] <= new_cost):
                continue

            explored_loc_cost_dict[movable_location] = new_cost
            frontier_queue.push(
                node=Node(
                    location=movable_location,
                    parent=current_node,
                    cost=new_cost,
                    heuristic=hueristic_method(movable_location)))
    return None


if __name__ == '__main__':
    maze: Maze = Maze()
    print(maze)

    goal_node: Optional[Node] = astar(
        init_loc=maze.start_loc,
        is_goal_loc_method=maze.is_goal_loc,
        get_movable_locations_method=maze.get_movable_locations,
        hueristic_method=maze.get_manhattan_distance,
    )
    if goal_node is None:
        print('Es ist ein Labyrinth, in dem der Ausgang nicht berechnet werden kann.')
    else:
        print('-' * 20)
        path: List[Location] = get_path_from_goal_node(
            goal_node=goal_node)
        maze.set_path_type_to_cells(path=path)
        print('Berechneter Pfad:')
        print(maze)

Beiseite

――Wenn ich ursprünglich Absolvent einer Schule im Bereich Design war, verzeihen Sie bitte Masakari, der über fundierte Kenntnisse im Bereich Informatik verfügt.

Referenzen / Site Summary

Recommended Posts

Schreiben Sie A * (A-Stern) -Algorithmen in Python
Schreiben Sie eine einfache Giermethode in Python
Schreiben Sie eine Dichotomie in Python
Schreiben Sie ein Kreisdiagramm in Python
Schreiben Sie das Vim-Plugin in Python
Schreiben Sie eine Suche mit Tiefenpriorität in Python
Implementierung eines einfachen Algorithmus in Python 2
Führen Sie einen einfachen Algorithmus in Python aus
Schreiben Sie den Test in die Python-Dokumentzeichenfolge
Schreiben Sie eine kurze Eigenschaftsdefinition in Python
Schreiben Sie ein Caesar-Verschlüsselungsprogramm in Python
Schreiben Sie ein einfaches Vim-Plugin in Python 3
Ein * Algorithmus (Python Edition)
Genetischer Algorithmus in Python
Schreiben Sie Python in MySQL
Algorithmus in Python (Bellman-Ford-Methode, Bellman-Ford)
Algorithmus in Python (Dijkstra)
Machen Sie einen Screenshot in Python
Schreiben Sie Pandec-Filter in Python
Erstellen Sie ein Wörterbuch in Python
Schreiben Sie die Beta-Distribution in Python
Algorithmus in Python (Haupturteil)
Reproduzieren Sie die euklidische Methode der gegenseitigen Teilung in Python
Algorithmus in Python (Dichotomie)
Erstellen Sie ein Lesezeichen in Python
Implementieren Sie den Dijkstra-Algorithmus in Python
Zeichne ein Herz in Python
Schreiben Sie ein super einfaches molekulardynamisches Programm in Python
Ich möchte in Python schreiben! (2) Schreiben wir einen Test
Schreiben Sie in Python ein logarithmisches Histogramm auf die x-Achse
Algorithmus in Python (Breitenprioritätssuche, bfs)
Schreiben Sie einen tabellengesteuerten Test in C.
Drücken Sie einen Befehl in Python (Windows)
Schreiben Sie ein JSON-Schema mit Python DSL
Erstellen Sie einen DI-Container mit Python
Es ist schwer, einen sehr einfachen Algorithmus in PHP zu schreiben
Schreiben Sie einen HTTP / 2-Server in Python
Zeichnen Sie eine Streudiagrammmatrix mit Python
Schreiben Sie die AWS Lambda-Funktion in Python
ABC166 in Python A ~ C Problem
Python-Algorithmus
Lassen Sie uns mit Python 2 einen Investitionsalgorithmus entwickeln
Erstellen Sie eine Binärdatei in Python
Algorithmus in Python (Tiefenprioritätssuche, dfs)
Schreiben Sie Selentestcode in Python
Löse ABC036 A ~ C mit Python
Algorithmus (Segmentbaum) in Python (Übung)
Löse ABC037 A ~ C mit Python
Zeichnen Sie ein CNN-Diagramm in Python
Erstellen Sie eine zufällige Zeichenfolge in Python
Schreiben Sie einen C-Sprach-Unit-Test in Python
Schreiben Sie ein Batch-Skript mit Python3.5 ~
Beim Schreiben eines Programms in Python
Generieren Sie eine erstklassige Sammlung in Python
Spiralbuch in Python! Python mit einem Spiralbuch! (Kapitel 14 ~)
Löse ABC175 A, B, C mit Python
Ein einfacher HTTP-Client, der in Python implementiert ist
Führen Sie eine nicht rekursive Euler-Tour in Python durch