八数码问题
在3×3的九宫格,摆有1至8的数字,和一个空格。
要求:给出一个初始状态和一个最终状态,找出从初始状态转变成最终状态的最少移动步数。
A搜索算法
1.A算法是基于估价函数的一种加权启发式图搜索算法。
2.估价函数 f(n) 定义为从初始结点经过 n 结点到达目的结点的路径的最小代价估计值。
3.启发式策略是利用与问题有关的启发信息引导搜索。
代码实现
- eightDigits.py
import copy
# [[向上 , 向下 , 向左 , 向右],
# [f(n) , 巽宫 , 离宫 , 坤宫],
# [d(n) , 震宫 , 中宫 , 兑宫],
# [w(n) , 艮宫 , 坎宫 , 乾宫],
# [空格的行下标 , 空格的列下标, 父状态的编号, 本状态的编号]
# f(n) 该状态的估值函数值
# d(n) 状态的深度
# w(n) 不在最终位置的数码个数
# f(n) = d(n) + w(n)
# 扩展方向的标志量,0:不行,1:可以
# 初始状态
Init = [[0, 1, 1, 1],
[7, 2, 0, 8],
[0, 1, 6, 3],
[7, 7, 5, 4],
[1, 2, 0, 0]]
# 最终状态
Final = [[0, 0, 0, 0],
[0, 1, 2, 3],
[0, 8, 0, 4],
[0, 7, 6, 5],
[2, 2, 0, 0]]
# 在open表中保留所有已生成而未扩展的状态
openList = list()
# 在closed表中记录已扩展过的状态。
closedList = list()
# 空格 上、下、左、右 移动时,横、纵坐标的变化量
move = [[-1, 0], [1, 0], [0, -1], [0, 1]]
# 记录当前八数码状态的编号的变量
num = [1]
def expandState():
# 父状态
parent = copy.deepcopy(closedList[-1])
# 空格 上、下、左、右 移动, 扩展状态
for k in range(4):
# 判断是否向该方向扩展
if parent[0][k] == 0:
continue
# 新的子扩展状态
child = copy.deepcopy(closedList[-1])
# 空格移动前的坐标
x1 = child[4][0]
y1 = child[4][1]
# 空格移动后的坐标
x2 = x1 + move[k][0]
y2 = y1 + move[k][1]
# 移动空格
temp = child[x2][y2]
child[x2][y2] = child[x1][y1]
child[x1][y1] = temp
# 更新空格的坐标
child[4][0] = x2
child[4][1] = y2
# 更新 d(n)
child[2][0] = child[2][0] + 1
# 计算 w(n)
wn = 0
for i in [1, 2, 3]:
for j in [1, 2, 3]:
if child[i][j] != Final[i][j]:
wn = wn + 1
# 中宫数码不在位,不计算在 w(n) 内
wn = wn-1 if child[2][2] != Final[2][2] else wn
# 更新 w(n)
child[3][0] = wn
# 更新 f(n)
child[1][0] = child[2][0] + wn
# 更新 上、下、左、右 是否扩展的标志量
child[0][0] = 1 if child[4][0] > 1 else 0
child[0][1] = 1 if child[4][0] < 3 else 0
child[0][2] = 1 if child[4][1] > 1 else 0
child[0][3] = 1 if child[4][1] < 3 else 0
# 不可以向反方向扩展
back = k-move[k][0]-move[k][1]
child[0][back] = 0
# 设置父状态的编号
child[4][2] = parent[4][3]
# 设置本状态的编号
child[4][3] = num[0]
# 编号加一, 待用
num[0] = num[0] + 1
# 扩展得到的子状态添加进 open 表
openList.append(child.copy())
# 清空
child.clear()
# 展示 open 表
def showOpen():
for ol in openList:
for i in [1, 2, 3]:
print("{} {} {}".format(ol[i][1], ol[i][2], ol[i][3]))
print(" f(n):{} d(n):{} w(n):{}".format(ol[1][0], ol[2][0], ol[3][0]))
# 展示 closed 表
def showClosed():
for cl in closedList:
for i in [1, 2, 3]:
print("{} {} {}".format(cl[i][1], cl[i][2], cl[i][3]))
print(" f(n):{} d(n):{} w(n):{}".format(cl[1][0], cl[2][0], cl[3][0]))
# 删除先前走错的状态结点,留下最终的路径
def find():
n = len(closedList)
m = closedList[-1][4][2]
# 从尾往前排查
for i in range(n-1, 0, -1):
# 如果前一个状态的编号不是当前状态的父状态编号
if closedList[i-1][4][3] != m:
# 不是当前状态的父状态编号,则删除
closedList.pop(i-1)
else:
# 是当前状态的父状态编号,则取得父状态的父状态编号
m = closedList[i-1][4][2]
# 算法具体步骤如下:
# Step 1:把附有f(S0)的初始结点 S0放入 open 表
# Step 2:若 open 表为空, 则搜索失败, 退出
# Step 3:移出 open 表中第一个结点 N 放入 closed 表
# Step 4:若目标结点把附有 f(S0) 的初始 S0 = N, 则搜索成功, 结束
# Step 5:若 N 不可扩展, 则转 Step 2
# Step 6:扩展 N, 生成一组附有 f(x) 的子结点, 加入 open 表
# Step 7:对 open 表按 f(x) 值以升序排序, 转 Step 2。
# Step 1
openList.append(Init)
# 循环调用
while (1):
#showOpen()
#showClosed()
# Step 2
if (len(openList) == 0):
print("搜索失败,退出程序!")
break
# Step 3
closedList.append(openList.pop(0))
# Step 4
if closedList[-1][3][0] == 0:
find()
print("closedList")
showClosed()
print("搜索成功!")
break
# Step 5
if (sum(closedList[-1][0]) == 0):
continue
# Step 6
expandState();
# Step 7
openList.sort(key = lambda x:(x[1][0], x[2][0], x[3][0]))
# 结束
print("\nEND !")