Figure-Depth First Search

Figure-Depth First Search

Knight travel question:

  1. In a chessboard mountain, a chess piece "horse", according to the rule of "horse walking day", starting from one square, it must travel all the chessboardsExactly once, Call such a sequence of moves as a "tour"

  2. Using graph search algorithm is one of the easiest solutions to understand and program the knight problem

  3. Solution:

    1. First, the legal move sequence is represented as a graph

      1. Use checkerboard as vertices
      2. Follow the steps of the "horse walk day" rule as the connecting edge
      3. Establish a checkerboard diagram that can be reached by all legal moves of each checkerboard
    2. Use the graph search algorithm to search for a path with a length of (row×column-1), which contains each vertex exactly once

    3. The key idea of ​​depth-first search to solve the knight's travel

      If the in-depth search along a single branch cannot continue (all legal movements have been walked), the path length has not reached the predetermined value (8×8 chessboard is 63), then the color mark is cleared and the previous layer is returned , Change a branch to continue to explore in depth

    4. Introduce a stack to record the path to facilitate the implementation of the backtracking operation of returning to the previous layer

  4. The graph search algorithm used to solve the knight travel problem is depth-first search

    ​ Depth-first search is to search as far as possible down a single branch of the tree. If you cannot continue to find the solution to the problem, go back to the previous layer and search for the next branch.

    A DFS algorithm is used to solve the knight travel problem, which is characterized by only visiting each vertex once

    Another DFS algorithm is more general, allowing vertices to be accessed repeatedly, which can be used as the basis for other graph algorithms

[External link image transfer failed. The source site may have an anti-hotlinking mechanism. It is recommended to save the image and upload it directly (img-w2KN4USk-1614326500835) (C:\Users\93623\AppData\Roaming\Typora\typora-user-images\ image-20210224145518265.png)]

[External link image transfer failed. The source site may have an anti-hotlinking mechanism. It is recommended to save the image and upload it directly (img-8V1VhFNp-1614326500839)(C:\Users\93623\AppData\Roaming\Typora\typora-user-images\ image-20210220175359142.png)]

def knightTour(n, path, u, limit):
    """

    :param n: 层次
    :param path: 路径
    :param u: 当前顶点
    :param limit: 搜索总深度
    :return:
    目前实现的算法,其复杂度为O(k^n),其中n是棋盘格数目
    """
    u.setColor('gray')
    # 当前顶点加入路径
    path.append(u)
    if n < limit:
        # 对所有合法移动逐一深入
        nbrList = list(u.getConnections())
        i = 0
        done = False
        while i < len(nbrList) and not done:
            # 选择白色未经过的顶点深入
            if nbrList[i].getColor() == 'white':
                # 层次加1,递归深入
                done = knightTour(n+1, path, nbrList[i], limit)
            i = i+1
        if not done:
            # 都无法完成总深度,回溯,试本层下一个顶点
            path.pop()
            u.setColor('white')
    else:
        done = True
    return done

Highlights of the above code:

One is the while loop

The second is recursive call

The third is to use gray and white to ensure only one visit

The complete code is as follows:

# coding: utf-8
# from . import graph_ccc
from GraphCode.graph_ccc import *


def genLegalMoves(x, y, bdsize):
    newMoves = []
    # 马走日8个格子
    moveOffsets = [(-1, -2), (-1, 2), (-2, -1), (-2, 1),
                   (1, -2), (1, 2), (2, -1), (2, 1)]
    for i in moveOffsets:
        newX = x + i[0]
        newY = y + i[1]
        if legalCoord(newX, bdsize) and legalCoord(newY, bdsize):
            newMoves.append((newX, newY))
    return newMoves


# 确认不会走出棋盘
def legalCoord(x, bdsize):
    if 0 <= x < bdsize:
        return True
    else:
        return False


# 构建走棋关系图
def knightGraph(bdsize):
    ktGrapth = Graph()
    # 遍历每个格子
    for row in range(bdsize):
        for col in range(bdsize):
            nodeId = posToNodeId(row, col, bdsize)
            # 单步合法走棋
            newPositions = genLegalMoves(row, col, bdsize)
            for e in newPositions:
                nid = posToNodeId(e[0], e[1], bdsize)
                # 添加边和顶点
                ktGrapth.addEdge(nodeId, nid)
    return ktGrapth


def posToNodeId(row, col, bdsize):
    """
    将坐标转化为id, row
    row和col都是从0开始的
    pos:  (0,0)(0,1)(0,2)(0,3),(0,4)
    id:    0     1     2    3     4
    :param row:
    :param col:
    :param bdsize:
    :return:
    """
    return row * bdsize + col

def orderbyAvail(n):
    resultList = []
    for v in n.getConnections():
        if v.getColor() == 'white':
            c = 0
            for w in v.getConnections():
                if w.getColor() == 'white':
                    c += 1
            resultList.append((c,v))
    resultList.sort(key=lambda x:x[0])
    return [y[1] for y in resultList]

def knightTour(n, path, u, limit):
    """
    knightTour(0, [], 4, 63)
    :param n: 层次, 是搜索树的当前深度
    :param path: 路径, 是到目前为止访问到的顶点列表
    :param u: 当前顶点, 是希望在图中访问的顶点
    :param limit: 搜索总深度, 路径上的顶点总数
    :return:
    目前实现的算法,其复杂度为O(k^n),其中n是棋盘格数目
    """
    u.setColor('gray')
    # 当前顶点加入路径
    path.append(u)
    if n < limit:
        # 对所有合法移动逐一深入
        # nbrList = list(u.getConnections())
        nbrList = list(orderbyAvail(u))
        i = 0
        done = False
        while i < len(nbrList) and not done:
            # 选择白色未经过的顶点深入
            if nbrList[i].getColor() == 'white':
                # 层次加1,递归深入
                done = knightTour(n + 1, path, nbrList[i], limit)
            i = i + 1
        if not done:
            # 都无法完成总深度,回溯,试本层下一个顶点
            path.pop()
            u.setColor('white')
    else:
        done = True
    return done


if __name__ == '__main__':
    g = knightGraph(8)
    # for i in g:
    #     print(i)

    path = []
    startVertex = g.getVertex(4)
    knightTour(0, path, startVertex, 63)
    # print(path)
    for node in path:
        print(node.getId(), end=" ")


The algorithm currently implemented has a complexity of O (kn) \mathbf(O)\left(\mathbf(k)^(n)\right)THE(kn ), where n is the number of checkers, such as8*8 chessboard, n is 64, k is the average number of branches, for each of 8*8, the average number of branches is 5 (that is, there are 5 ways to move in each grid on average)

This is an algorithm of exponential time complexity, and its search process is represented as a tree with level n

  1. Even exponential time complexity algorithms can be greatly improved in actual performance

    1. The smart structure of nbrList, arranging the order of the vertices in a specific way, can reduce the search time of the travel path of the 8×8 board to the second level
  2. This improved algorithm is called Warnsdorff algorithm


General depth first search

  1. The general goal of depth-first search is to search as deep as possible on the graph, connect as many vertices as possible, branch if necessary (create trees), and sometimes create multiple trees, called "depth-first forest"

  2. Depth-first search also uses the "precursor" attribute of the vertex to build a tree or forest

    1. In addition, you need to set the "discovery time" and "end time" attributes. The former means that the vertex is visited in the first few steps (set to gray), and the latter refers to the completion of the exploration of this vertex in the first few steps (set to black)
    2. These two new attributes are very important for the following graph algorithm
  3. The graph with the DFS algorithm is implemented as a subclass of Graph

    1. Vertex adds members Discovery and Finish
    2. Graph Graph adds member time to record the number of steps executed by the algorithm
  4. The tree constructed by DFS has the attributes of "discovery time" and "end time" at its vertices, which are similar to parentheses.

    1. That is, the "discovery time" of a vertex is always less than the "discovery time" of all sub-vertices
    2. The "end time" is greater than the "end time" of all sub-vertexes, which is discovered earlier than the sub-vertex, and the exploration is ended later
  5. DFS runtime also includes two aspects

    1. There are two loops in the dfs function, each of which is |V| times, so it is o(|V|)
    2. The loop in the dfsvisit function is performed on the vertices connected to the current vertex, and the recursive call is only made when the vertex is white, so only one step is run for each edge, so it is o(|E |)
    3. Add up to the same o(|V|+|E|) as BFS

BFS uses queues to store vertices to be accessed

DFS uses recursive calls and implicitly uses the stack

# coding: utf-8

from pythonds.graphs import Graph


class DFSGraph(Graph):
    def __init__(self):
        super().__init__()
        self.time = 0

    def dfs(self):
        # 颜色的初始化
        for aVertex in self:
            aVertex.setColor('white')
            aVertex.setPred(-1)
        for aVertex in self:
            # 如果还有未包括的顶点,则键森林
            if aVertex.getColor() == 'white':
                self.dfsvisit(aVertex)

    def dfsvisit(self, startVertex):
        startVertex.setColor('gray')
        # 算法的步数加一
        self.time += 1
        startVertex.setDiscovery(self.time)
        for nextVertex in startVertex.getConnections():
            if nextVertex.getColor() == 'white':
                nextVertex.setPred(startVertex)
                # 深度优先递归访问
                self.dfsvisit(nextVertex)
        startVertex.setColor('black')
        self.time += 1
        startVertex.setFinish(self.time)

Guess you like

Origin blog.csdn.net/weixin_46129834/article/details/114137276