八数码难题——A*

259 阅读3分钟

原理

盲目搜索要求对某个状态的每一个方向都进行扩展。A*算法要求对某个状态的每一个方向都进行分析,但只扩展代价最小的那个子方向节点(如果有相同的则随机选择一个)。

 

代码由BFS版本的代码修改得到。

程序修改的方法就是在探索方向的for循环里顺便计算四个(最多四个)子方向的代价值,选择一个代价最小的方向作为最终的方向final去让空白格移动。然后判断新状态是否已分析,若没有,则进行原始的操作;若已经处理过了,则跳过。当然,这个过程要换到for循环外面。同时,因为选择的方向已经是最优的,所以操作以后不需要让空白格回退 。

有一个需要注意的地方:因为有 “代价相同时随机选择一个方向” 和 “只能处理先前未处理过的状态” 这两个要求。所以对于某些棋盘的最终步数,甚至是是否有可行解有很大影响。

 

程序实现

import sys
import queue
import numpy as np
np.set_printoptions(threshold=np.inf)  # threshold 指定超过多少使用省略号,np.inf代表无限大

start = None
end = '123804765'
dict = {}  # 用来做状态处理的标记  相当于open表
ans = np.zeros(400000, dtype=int)  # 如果开的小,很多棋盘会解不出来


# 求当前状态与目标状态的偏差代价
def bias(str):
    sum = 0
    for i in range(9):
        if str[i] != '0' and end[i] != '0' and str[i] == end[i]:  # 不把0算入在内
            sum = sum + 1
    return 8 - sum


# 描述空白格移动
def swap(str, zero, to):
    temp = list(str)  # 在python中,字符串是不可变的变量,所以先转化为list
    temp[zero], temp[to] = temp[to], temp[zero]
    now = ''.join(temp)
    return now


# 求初始状态的逆序数判断是否合法
def inversion(str):
    sum = 0
    for i in range(9):  # 0-8
        for j in range(i + 1, 9):
            if str[j] < str[i] and str[j] != '0':  # 不把0算入在内
                sum = sum + 1
    return sum


# 试探空白格移动的四个方向
def move(i, index):
    if i == 1:  # 向上
        if index - 3 >= 0:
            return index - 3
        else:
            return -1
    elif i == 2:  # 向下
        if index + 3 <= 8:
            return index + 3
        else:
            return -1
    elif i == 3:  # 向左
        if index % 3 != 0:
            return index - 1
        else:
            return -1
    elif i == 4:  # 向右
        if index % 3 != 2:
            return index + 1
        else:
            return -1


def A(start):
    head, tail, flag = 0, 1, False

    q = queue.Queue()  # 括号中是队列可容纳数据的多少,如果不设置,则可以一直增加
    q.put(start)

    while not (q.empty() or flag):
        now = q.get()  # 取出的时候顺带弹出了.  now现在是str类型
        zero = now.find('0')  # 寻找0的位置

        min = 1000
        print('当前状态分支节点的代价情况:')
        # 向四个方向寻找
        for i in range(1, 5):
            to = move(i, zero)

            if to == -1:
                continue
            now = swap(now, zero, to)  # 交换,计算当前状态其他方向的f(x)代价

            if (now not in dict) and (now != start):  # 对于已经分析过的状态(包括初始状态),不再计算它们的代价值
                g = ans[head] + 1  # 深度代价
                h = bias(now)  # 偏差代价
                print(g, h)

                f = g + h  # 总代价

                if f < min:
                    min = f
                    final = to

            now = swap(now, zero, to)  # 恢复现场,以便去计算原状态下一个方向的f(x)代价
        print('\n')

        now = swap(now, zero, final)  # 已寻找到最好的方向,交换
        print('step:' + str(ans[head] + 1) + '  移动后的状态:' + now)
        if now not in dict:
            if now == end:
                print(ans[head] + 1, end=' steps')  # 深度
                flag = True

            ans[tail] = ans[head] + 1
            tail = tail + 1

            dict[now] = i  # 该状态已分析过,做标记
            q.put(now)
        else:
            print('因代价等量时遵循随机选择策略,导致没有其他未走过的路径可走')
        # now = swap(now, zero, final)  # 已经找到最好的方向了,不用再换过来

        head = head + 1


if __name__ == '__main__':
    # start = input()  # 输入初始状态
    start = '283164705'  # 283104765:4     283164705:5   216408753:18   234150768:无解

    if start == end:
        print('已是终点状态')
        sys.exit()

    if inversion(start) % 2 != inversion(end) % 2:
        print('该状态无解')
        sys.exit()

    print('初始状态:' + start)
    A(start)

 

程序运行结果