原理
盲目搜索要求对某个状态的每一个方向都进行扩展。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)