10 minutes to teach you how to use python animation to demonstrate the depth-first algorithm to find the way out of the maze

@This article comes from the public number: csdn2299, like to pay attention to the public number programmers school What is the
depth first algorithm (DFS algorithm)?

The algorithm for finding the path between the starting node and the target node is often used to search for the path to escape the maze. The main idea is that starting from the entrance, the possible coordinates of the surrounding nodes are searched in order, but they will not pass through the same node repeatedly and cannot pass through the obstacle nodes. If you go to a node and find that there is no way to go, then you will fall back to the previous node and choose another path. Until the exit is found, or there is no way to go back to the starting point, the game is over. Of course, the depth-first algorithm will stop searching as long as it finds a working path; that is, as long as there is a way to go, the depth-first algorithm will not fall back to the previous step.

If you are still confused in the world of programming, you can join our Python learning button qun: 784758214 to see how the predecessors learned! Exchange of experience! I am a senior python development engineer, from basic python scripts to web development, crawlers, django, data mining, etc., from zero basis to project actual combat information. To every little friend of python! Share some learning methods and small details that need attention, click to join our python learner gathering place

The following figure is a path searched using the DFS algorithm: Insert picture description here
Summarize:

Starting from the starting point, query the nodes that can go through the next step, and push these possible nodes into the stack, and the nodes that have already passed will not try again. After the query is completed, remove a node from the stack and query whether there is a working node around the node. If there is no possible node, continue to take a node from the stack. Repeat the above operation until the current node is the end point, or there is no more node in the stack.

Define the data:

Start node and target node
Storage node stack
Define auxiliary functions

Get the function of the next node: successor function to
determine whether it is the end point: test_goal
First, let's define the data structure of the stack. The stack is a last-in, first-out data structure.

Because the breadth-first search will use the queue later, and the A * algorithm will use the priority queue, we have defined an abstract base class for subsequent use. deque is a double-ended queue, similar to the built-in type list operation, but the time complexity of the head and tail insertion and deletion operations are both O (1).

# utils.py
from abc import abstractmethod, ABC
from collections import deque
class Base(ABC):
  def __init__(self):
    self._container = deque()
  @abstractmethod
  def push(self, value):
    """push item"""
  @abstractmethod
  def pop(self):
    """pop item"""
  def __len__(self):
    return len(self._container)
  def __repr__(self):
    return f'{type(self).__name__}({list(self._container)})'
class Stack(Base):
  def push(self, value):
    self._container.append(value)
  def pop(self):
    return self._container.pop()

Next, let's define the dfs function. Among them, initial is the initial node, s is the stack, and marked is used to record the passing nodes. The successor function is used to search for the next possible node, and the test_goal function is used to determine whether the node is the target node. children is a list of possible nodes, traverse these nodes, push the nodes that have not traveled onto the stack, and make a record.

# find_path.py
from utils import Stack
def dfs(initial, _next = successor, _test = test_goal):
  s: Stack = Stack()
  marked = {initial}
  s.push(initial)
  while s:
    parent: state = s.pop()
    if _test(parent):
      return parent
    children = _next(parent)
    for child in children:
      if child not in marked:
        marked.add(child)
        s.push(child)

Next, we use the DFS algorithm to find the maze path, and visually demonstrate the searched maze path.

First, use an enumeration to indicate the color of the path. EMPTY is a normal node, BLOCKED is a barrier node, START is a maze entrance, END is a maze exit, and PATH is a search path.

from enum import IntEnum
class Cell(IntEnum):
  EMPTY = 255
  BLOCKED = 0
  START = 100
  END = 200
  PATH = 150

Next, let's define the maze. First, we use Namedtuple to define the coordinates of each node of the maze:

class MazeLocation(NamedTuple):
  row: int
  col: int

First of all, in order to facilitate the determination of the relationship between nodes, we define an internal class _Node in the Maze class to record the state of the node and the parent node of the node.

class _Node:
  def __init__(self, state, parent):
    self.state = state
    self.parent = parent

Then initialize, determine the coordinates of the entrance and exit, use the np.random.choice function to randomly generate the maze, and mark the entrance and exit.

def __init__(self, rows: int = 10, cols: int = 10,
       sparse: float = 0.2, seed: int = 365,
       start: MazeLocation = MazeLocation(0, 0),
       end: MazeLocation = MazeLocation(9, 9), *,
       grid: Optional[np.array] = None) -> None:
  np.random.seed(seed)
  self._start: MazeLocation = start
  self._end: MazeLocation = end
  self._grid: np.array = np.random.choice([Cell.BLOCKED, Cell.EMPTY],
                        (rows, cols), p=[sparse, 1 - sparse])
  self._grid[start] = Cell.START
  self._grid[end] = Cell.END

The second is the test_goal method, as long as the node coordinates are relative to the target node.

def _test_goal(self, m1: MazeLocation) -> bool:
  return m1 == self._end

Then there is the successor method. As long as the nodes in the up, down, left, and right directions are not obstacles and are within the boundary, they are taken into consideration and added to the list.

List[MazeLocation]:
  location: List[MazeLocation] = []
  row, col = self._grid.shape
  if m1.row + 1 < row and self._grid[m1.row + 1, m1.col] != Cell.BLOCKED:
    location.append(MazeLocation(m1.row + 1, m1.col))
  if m1.row - 1 >= 0 and self._grid[m1.row - 1, m1.col] != Cell.BLOCKED:
    location.append(MazeLocation(m1.row - 1, m1.col))
  if m1.col + 1 < col and self._grid[m1.row, m1.col + 1] != Cell.BLOCKED:
    location.append(MazeLocation(m1.row, m1.col + 1))
  if m1.col - 1 >= 0 and self._grid[m1.row, m1.col - 1] != Cell.BLOCKED:
    location.append(MazeLocation(m1.row, m1.col - 1))
  return location

The display path, pause is the interval of displaying the image, and plot is the drawing flag. Starting from the target node, traverse the parent node of each node until it reaches the initial node, and draw a path map.

None:
  if pause <= 0:
    raise ValueError('pause must be more than 0')
  path: Maze._Node = self._search()
  if path is None:
    print('没有找到路径')
    return
  path = path.parent
  while path.parent is not None:
    self._grid[path.state] = Cell.PATH
    if plot:
      self._draw(pause)
    path = path.parent
  print('Path Done')

In order to use the DFS algorithm, we define the DepthFirstSearch class, inheriting the maze class. The DepthFirstSearch class rewrites the _search method of the base class, which is almost the same as the dfs function definition we defined earlier.

class DepthFirstSearch(Maze):
  def _search(self):
    stack: Stack = Stack()
    initial: DepthFirstSearch._Node = self._Node(self._start, None)
    marked: Set[MazeLocation] = {initial.state}
    stack.push(initial)
    while stack:
      parent: DepthFirstSearch._Node = stack.pop()
      state: MazeLocation = parent.state
      if self._test_goal(state):
        return parent
      children: List[MazeLocation] = self._success(state)
      for child in children:
        if child not in marked:
          marked.add(child)
          stack.push(self._Node(child, parent))
class DepthFirstSearch(Maze):
  def _search(self):
    stack: Stack = Stack()
    initial: DepthFirstSearch._Node = self._Node(self._start, None)
    marked: Set[MazeLocation] = {initial.state}
    stack.push(initial)
    while stack:
      parent: DepthFirstSearch._Node = stack.pop()
      state: MazeLocation = parent.state
      if self._test_goal(state):
        return parent
      children: List[MazeLocation] = self._success(state)
      for child in children:
        if child not in marked:
          marked.add(child)
          stack.push(self._Node(child, parent))

Thank you very much for reading
. When I chose to study python at university, I found that I ate a bad computer foundation. I did n’t have an academic qualification. This is
nothing to do. I can only make up for it, so I started my own counterattack outside of coding. The road, continue to learn the core knowledge of python, in-depth study of computer basics, sorted out, if you are not willing to be mediocre, then join me in coding, and continue to grow!
In fact, there are not only technology here, but also things beyond those technologies. For example, how to be an exquisite programmer, rather than "cock silk", the programmer itself is a noble existence, isn't it? [Click to join] Want to be yourself, want to be a noble person, come on!

34 original articles published · Liked12 · Visitors 20,000+

Guess you like

Origin blog.csdn.net/chengxun03/article/details/105476985