Python experiment — handwritten a * algorithm to realize eight digital problems

Time:2022-1-23

1. Problem description

For problem: Path Search

Problem Description: A, B, C N nodes, each node has defined whether it can reach and path cost, and the goal is to search the best path

Eight digit problem: there are 1-8 eight numbers in the 3 * 3 Jiugong grid, and the remaining one is a space. You can only move the space once at a time. Given the initial state and target state, you can find the best moving method and the shortest moving distance

2. Principle of a algorithm

  1. First, put the initial node into the open table
  2. Put the initial node into the closed table, expand the node in all directions from the initial node, and put the new extended node into the open table
  3. The cost is calculated according to the path corresponding to the new extension node, and then sorted
  4. Select the node with the lowest cost from the open table as the current node and put it into the closed table
  5. Repeat steps 2 and 3.4 until the target node is found and the best path is returned

3. A * algorithm requirements

The difference between a * algorithm and a algorithm lies in the requirements for the formula of path calculation cost

Algorithm A: cost = historical cost + future cost (heuristic function), where historical cost refers to the known cost of walking to the current state, and future cost refers to the estimated cost from the current node to the target state

A * algorithm: the estimated cost must be greater than or equal to the real future cost

4. How to judge whether there is an reachable path between given states

The concept of reverse order number is introduced: in an arrangement, if the front and rear positions of a logarithm are opposite to the size order, that is, the front number is greater than the rear number, then they are called a logarithmReverse order。 The total number in reverse order in an arrangement is called the total number of the arrangementReverse order number

After calculating the reverse order numbers of the initial state and the target state, if the parity of the two states is consistent, they can be reached, otherwise, they cannot be reached

5. Specific code implementation

5.1. Subfunction definition

5.1.1. Reverse order number calculation

def judge(number):
    total = 0
    data = [9, 9, 9, 9, 9, 9, 9, 9, 9]
    for i in range(9):
        if number[i] != '0':
            data[i] = int(number[i])
        else:
            data[i] = 0
    for i in range(9):
        for j in range(i):
            if data[i] * data[j] != 0:
                if data[j] > data[i]:
                    total = total + 1
    return total

5.1.2. Adjust node location based on cost

def addOpen(node):
        if len(open) == 0 or node[2] >= open[-1][2]:
            open.append(node)
        else:
            for i in range(len(open)):
                if node[2] < open[i][2]:
                    open.insert(i, node)
                    break

5.1.3. Heuristic function (Manhattan distance)

def manhattan(src, dst):
    total = 0
    pos_src = [[0 for x in range(2)] for y in range(9)]
    pos_dst = [[0 for x in range(2)] for y in range(9)]
    for i in range(9):
        x = i // 3
        y = i % 3
        pos_src[int(dst[i])][0] = x
        pos_src[int(dst[i])][1] = y
        pos_dst[int(src[i])][0] = x
        pos_dst[int(src[i])][1] = y
    for i in range(9):
        total = total + abs(pos_src[i][0]-pos_dst[i][0]) + abs(pos_src[i][1]-pos_dst[i][1])

    return total

5.1.4. Returns the position of 0 in the sequence

def position(src):
    flag = src.index('0')
    row = int(flag // 3)
    col = int(flag % 3)
    return [flag, row, col]

5.1.5. change of position

def exchange(src, x, y, x2, y2):
    flag = x * 3 + y
    flag2 = x2 * 3 + y2
    tmp1 = src[flag]
    tmp2 = src[flag2]
    dst = copy.copy(src)
    dst[flag] = tmp2
    dst[flag2] = tmp1
    return dst

5.1.6. Print node status

def prtNum(src):
    for x in range(3):
        for y in range(3):
            print(str(src[x * 3 + y] + ' '), end='')
        print()

5.2. Extended node function

Information carried by each node in the table:

[this node description, parent node code, this node cost, this node code, whether it is in target status]

def expand(src, side):
    global crt
    global nodeid
    pos = position(src)
    x = pos[1]
    y = pos[2]
    rtResult = []

    #Calculate the historical cost of a node (by finding its parent node to the initial node)
    depth = 0
    nodePrt = open[0][4]
    if nodePrt == 0:
        depth = 0
    else:
        while True:
            for i in range(len(closed)):
                if nodePrt == closed[i][3]:
                    depth = depth + 1
                    nodePrt = closed[i][4]
            if nodePrt == 0:
                break

    #Extend left
    if side == 'left' or side == '':
        if y > 0:
            if_final = 0
            crtLeft = exchange(src, x, y, x, y - 1)
            leftCost = manhattan(numberFinal, crtLeft) + depth
            nodeid = nodeid + 1
            if manhattan(numberFinal, crtLeft) == 0:
                if_final = 1
            rtResult.append([crtLeft, src, leftCost, nodeid, if_final])
    #Extend right
    if side == 'right' or side == '':
        if y < 2:
            if_final = 0
            crtRight = exchange(src, x, y, x, y + 1)
            rightCost = manhattan(numberFinal, crtRight) + depth
            nodeid = nodeid + 1
            if manhattan(numberFinal, crtRight) == 0:
                if_final = 1
            rtResult.append([crtRight, src, rightCost, nodeid, if_final])
    #Expand up
    if side == 'up' or side == '':
        if x > 0:
            if_final = 0
            crtUp = exchange(src, x, y, x - 1, y)
            upCost = manhattan(numberFinal, crtUp) + depth
            nodeid = nodeid + 1
            if manhattan(numberFinal, crtUp) == 0:
                if_final = 1
            rtResult.append([crtUp, src, upCost, nodeid,if_final])
    #Extend down
    if side == 'down' or side == '':
        if x < 2:
            if_final = 0
            crtDown = exchange(src, x, y, x + 1, y)
            depth = depth + 1
            downCost = manhattan(numberFinal, crtDown) + depth
            nodeid = nodeid + 1
            if manhattan(numberFinal, crtDown) == 0:
                if_final = 1
            rtResult.append([crtDown, src, downCost, nodeid, if_final])
    return rtResult

5.3. Open table handler

def handleOpen():
    Global nodeid # records the number of extension nodes
    global open
    while True:
        if len(open) == 0:
            break
        x = 0
        tmpOpen = open[0]

        tmp = expand(open[0][0], '')
        for y in range(len(tmp)):
            flag = False
            for j in range(len(open)):
                if tmp[y][0] == open[j][0]:
                    flag = True
            for k in range(len(closed)):
                if tmp[y][0] == closed[k][0]:
                    flag = True
            if not flag:
                addOpen([tmp[y][0], tmp[y][1], tmp[y][2], tmp[y][3], open[x][3]])

            If TMP [y] [4] = = 1: # judge whether to reach the final node
                closed.append(tmpOpen)
                closed.append(open[0])
                open.remove(open[0])
                print('Totally', nodeid, 'nodes ayalyzed,find the result.')
                prtResult()
                print('Success!')
                exit("We find it!")
        closed.append(tmpOpen)
        open.remove(tmpOpen)

5.4. Backtracking by node code

#Start from the last item of the close table, find the previous node until the previous node is 0, write all the found sequences to step and print out step
def prtResult():
    step = [closed[-1]]
    nodePrt = closed[-1][4]
    while True:
        for x in range(len(closed)):
            if nodePrt == closed[x][3]:
                step.insert(0, closed[x])
                nodePrt = closed[x][4]
        if nodePrt == 0:
            break
    for x in range(len(step)):
        print('Step', x, ':')
        prtNum(step[x][0])
    print('Finished!')
    time.sleep(10)

5.5. Main function

if __name__ == '__main__':
    #Initialization
    open = []
    closed = []
    nodeid = 1

    #Enter the initial and target sequences and print them out for confirmation
    while True:
        print('Please input Original state:', end='\t')
        tmp = input()
        numberOrig = [tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8]]
        print('Please input Final state:', end='\t')
        tmp = input()
        numberFinal = [tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8]]
        print('Orig is')
        prtNum(numberOrig)
        print('Final is')
        prtNum(numberFinal)
        if (judge(numberOrig) + judge(numberFinal)) % 2 == 0:
            print('Have answer! Orig is ', judge(numberOrig), ', Final is', judge(numberFinal))
            #Extract the digital location of the target node

            #Add the initial node to the open table and start processing.
            open.append([numberOrig, 'NULL', manhattan(numberOrig, numberFinal), 1, 0, 0])
            handleOpen()
        else:
            print('No answer! Orig is ', judge(numberOrig), ', Final is', judge(numberFinal))

6. Test results

Please input Original state:	012345678
Please input Final state:	123450678
Orig is
0 1 2 
3 4 5 
6 7 8 
Final is
1 2 3 
4 5 0 
6 7 8 
Have answer! Orig is  0 , Final is 0
Totally 474 nodes ayalyzed,find the result.
Step 0 :
0 1 2 
3 4 5 
6 7 8 
Step 1 :
1 0 2 
3 4 5 
6 7 8 
Step 2 :
1 2 0 
3 4 5 
6 7 8 
Step 3 :
1 2 5 
3 4 0 
6 7 8 
Step 4 :
1 2 5 
3 0 4 
6 7 8 
Step 5 :
1 2 5 
0 3 4 
6 7 8 
Step 6 :
0 2 5 
1 3 4 
6 7 8 
Step 7 :
2 0 5 
1 3 4 
6 7 8 
Step 8 :
2 5 0 
1 3 4 
6 7 8 
Step 9 :
2 3 5 
1 0 4 
6 7 8 
Step 10 :
2 3 5 
1 4 0 
6 7 8 
Step 11 :
2 3 0 
1 4 5 
6 7 8 
Step 12 :
2 0 3 
1 4 5 
6 7 8 
Step 13 :
0 2 3 
1 4 5 
6 7 8 
Step 14 :
1 2 3 
0 4 5 
6 7 8 
Step 15 :
1 2 3 
4 0 5 
6 7 8 
Step 16 :
1 2 3 
4 5 0 
6 7 8 
Finished!
Success!
We find it!