我们使用一个二维数组arr表示一个状态空间,数组的元素含有0-8,0表示可以移动的空格,1-8表示其余待排序的方块。【一个二维数组表示八数码的每个位置的可移动方向】通过对空格0的上、下、左、右移动,得到最终的目标状态。
为实现BFS和DFS搜索算法,我们需要实现一些辅助函数:
① Cal_sameNum(self, state):传入一个状态state,返回当前节点”在位“的棋格数。
② NeedStop(self, state, stop):传入一个状态state和一个状态列表stop,若状态state与列表stop中某一状态完全一致,则返回True,意味着终止搜索。
③ Move(self):将self.arr所在状态的下一步可能移动到的状态的集合,存入一个列表Move_list并返回。
【Move函数可以用字典进行优化】
奇偶性一致才有解
http://www.voidcn.com/article/p-gvpoulek-dd.html
通过栈stack(先进后出)进行实现。
① 起始状态放入栈中。
② 判断栈是否为空,栈空则直接无解,失败退出。
③ 显示栈顶元素的状态
④ 判断栈顶元素状态是否到达目标状态,若到达则意味着找到了最终解,未到达则继续循环操作。
⑤ 通过Move函数得到栈顶元素的下一步状态列表,然后将下一步状态依次入栈,计数+1
由于仅使用上述实现方法效率过低,甚至会进入死循环,我们将其进行优化:
每次入栈前增加两个判断:
① 是否超出一个节点拓展的最大深度,如最大深度为5。
② 是否在stop列表中出现,防止出现与前面出现过的状态相同,以此进入死循环。
通过队列queue(先进先出)进行实现,其余通DFS。
https://blog.csdn.net/Suyebiubiu/article/details/101194332
改进优先队列
f(x) = g(x) + h(x)
g(x)为节点的深度。
h(x)可以有多种选择,例如“不在位”数、曼哈顿距离、欧氏距离等。
完整代码如下:
# -*- encoding: utf-8 -*-
# %%
import numpy as np
import time
import queue
# @ 判断是否有解->计算状态的逆序数,若逆序数为奇数则有解(与目标状态的逆序数同为奇数)
def HaveSolution(arr):
numList, sum = [], 0
for i in range(3):
for j in range(3):
numList.append(arr[i, j])
for index, val in enumerate(numList):
if index < 9:
for i in range(index, 9):
if val > numList[i] and numList[i] != 0:
sum += 1
return sum % 2 == 1
# @ 展示到达目标的最短路径
def Display_path(res, count, node):
result = []
if res:
print('经过%d次变换结束' % count)
while node:
result.append(node)
node = node.parent
result.reverse()
for node in result:
for i in range(3):
for j in range(3):
print(node.arr[i,j], end='\t')
print("\n")
print('->')
else:
print('未找到合适路径!')
print('----------------------------------------------')
# @ 计算"不在位"的棋个数
def Cal_diffNum(arr, target):
pos = np.where(arr != target)
return len(arr[pos])
# @ A*中优先队列的pop(根据"不在位"棋个数)
def pop_smallest_costNum(nodes,target):
cost_list = []
for node in nodes:
cost_list.append(Cal_diffNum(node.arr,target))
temp = nodes[cost_list.index(min(cost_list))]
nodes.remove(nodes[cost_list.index(min(cost_list))])
return temp
# @ 计算某个状态下每个棋子到目标位置的曼哈顿距离之和
def Cal_distance(arr, taregt):
sum = 0
for cols in taregt:
for val in cols:
i,j = np.where(arr==val)[0], np.where(arr==val)[1]
si,sj = np.where(taregt==val)[0], np.where(taregt==val)[1]
sum += abs(i-si)+abs(j-sj)
return sum
# @ A*中优先队列的pop(根据曼哈顿距离之和)
def pop_smallest_costDis(nodes, target):
cost_list = []
for node in nodes:
cost_list.append(Cal_distance(node.arr, target))
temp = nodes[cost_list.index(min(cost_list))]
nodes.remove(nodes[cost_list.index(min(cost_list))])
return temp
# @ 计算cost
def compute_cost(depth, type, arr, target):
if type=='DFS' or type=='BFS':
return depth+1
elif type=='UCS':
return Cal_diffNum(arr, target)
elif type=='A_star1':
return depth + Cal_diffNum(arr, target)
elif type=='A_star2':
return depth + Cal_distance(arr, target)
else:
print('ERROR!')
return 0
# @ 八数码节点类,即某一状态
class EightNumArr(object):
directions = ['left','up','right','down']
directions2 = ['down','right','up','left']
def __init__(self, arr, target, type, cost=0, parent=None, depth=0):
self.arr = arr
self.target = target
self.type = type
self.cost = cost
self.parent = parent
self.depth = depth
def __lt__(self, other):
# return Cal_diffNum(self.arr, self.target)
return compute_cost(self.depth,self.type,self.arr,self.target) < compute_cost(other.depth,self.type,other.arr,other.target)
# @ 打印八数码
def Display(self):
for i in range(3):
for j in range(3):
print(self.arr[i, j], end='\t')
print("\n")
print('->')
# @ 返回该状态与目标状态中数字位置相同的个数-->返回9时则结束
def Cal_sameNum(self, state, target):
pos = np.where(state.arr == target)
return len(state.arr[pos])
# @ 计算曼哈顿距离,作为后续A*2中每轮循环下排序的标准
def Cal_Dis(self, state, target):
sum = 0
for cols in target:
for val in cols:
i, j = np.where(state.arr == val)[0], np.where(state.arr== val)[1]
si, sj = np.where(target == val)[0], np.where(target == val)[1]
sum += abs(i - si) + abs(j - sj)
return sum
# @ 是否在close表中已出现,若已出现还放入open表,则会进入死循环。
def isInClose(self, state, stop):
for val in stop:
pos = np.where(state.arr == val.arr)
if len(state.arr[pos]) == 9:
return True
return False
# @ 某一状态下进行四个方向上的移动空格
def Move(self, type):
row, col = np.where(self.arr == 0)
if type=='DFS':
directs, Move_list = self.directions2,[]
else:
directs, Move_list = self.directions,[]
for direct in directs:
if 'left' == direct and col > 0:
arr_copy = self.arr.copy()
arr_copy[row, col], arr_copy[row, col - 1] = arr_copy[row, col - 1], arr_copy[row, col]
Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target),self, self.depth + 1))
elif 'up' == direct and row > 0:
arr_copy = self.arr.copy()
arr_copy[row, col], arr_copy[row - 1, col] = arr_copy[row - 1, col], arr_copy[row, col]
Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target), self, self.depth + 1))
elif 'down' == direct and row < 2:
arr_copy = self.arr.copy()
arr_copy[row, col], arr_copy[row + 1, col] = arr_copy[row + 1, col], arr_copy[row, col]
Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target), self, self.depth + 1))
elif 'right' == direct and col < 2:
arr_copy = self.arr.copy()
arr_copy[row, col], arr_copy[row, col + 1] = arr_copy[row, col + 1], arr_copy[row, col]
Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target), self, self.depth + 1))
return Move_list
# @ DFS深度优先搜索,返回True/False, count搜索次数
def DFS(self,max_depth):
start = time.time()
stack, close, count, node = [], [], 0, None
stack.append(self)
while stack:
node = stack.pop() # 后进先出,pop出栈顶元素
close.append(node)
# node.Display()
if self.Cal_sameNum(node,self.target) == 9: # 到达目标状态,直接返回
end = time.time()
return True, count, node, end-start
next_nodes = node.Move('DFS') # 一次移动的状态列表
for next_node in next_nodes:
if next_node.depth < max_depth: # 最大深度
if self.isInClose(next_node, close):
continue
stack.append(next_node)
count += 1
end = time.time()
return False, count, node, end-start
# @ BFS广度优先搜索,返回True/False, count搜索次数
def BFS(self,max_depth):
start = time.time()
queue, close, count, node = [], [], 0, None
queue.append(self)
while queue:
node = queue.pop(0) # 先进先出,pop出队首元素
close.append(node)
# node.Display()
if self.Cal_sameNum(node,self.target) == 9: # 到达目标状态,直接返回
end = time.time()
return True, count, node, end-start
next_nodes = node.Move('BFS') # 一次移动的状态列表
for next_node in next_nodes:
if next_node.depth < max_depth: # 最大深度
if self.isInClose(next_node, close):
continue
queue.append(next_node)
count += 1
end = time.time()
return False, count, node, end-start
# @ UCS一致代价搜索,返回True/False,count搜索次数
def UCS(self,max_depth):
start = time.time()
close, count, node = [], 0, None
que = queue.PriorityQueue()
que.put(self)
while que:
node = que.get()
close.append(node)
# node.Display()
if self.Cal_sameNum(node, self.target) == 9: # 到达目标状态,直接返回
end = time.time()
return True, count, node, end-start
next_nodes = node.Move('UCS') # 一次移动的状态列表
for next_node in next_nodes:
if next_node.depth < max_depth:
if self.isInClose(next_node, close):
continue
que.put(next_node)
count += 1
end = time.time()
return False, count, node, end-start
# @ A_star启发式搜索--h(x):不在位数量/曼哈顿距离,返回True/False,count搜索次数
def A_star(self,max_depth,A_starType):
start = time.time()
close, count, node = [], 0, None
que = queue.PriorityQueue()
que.put(self)
while que:
node = que.get()
close.append(node)
if self.Cal_sameNum(node, self.target) == 9: # 到达目标状态,直接返回
end = time.time()
return True, count, node, end-start
next_nodes = node.Move(A_starType) # 一次移动的状态列表
for next_node in next_nodes:
if next_node.depth < max_depth: # 最大深度
if self.isInClose(next_node, close):
continue
que.put(next_node)
count += 1
end = time.time()
return False, count, node, end-start