实验二 A*算法求解八数码问题实验
一、实验目的:
熟悉和掌握启发式搜索的定义、估价函数和算法过程,并利用A*算法求解N数码难题,理解求解流程和搜索顺序。
二、 实验内容
以8数码问题和15数码问题为例实现A*算法的求解程序(编程语言不限,如Python等),要求设计两种不同的估价函数。
三、实验要求
1.设置相同的初始状态和目标状态,针对不同的估价函数,求得问题的解,比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数等,填入表3。
2.设置与上述1相同的初始状态和目标状态,用宽度优先搜索算法(即令估计代价h(n)=0的A*算法)求得问题的解,以及搜索过程中的扩展节点数、生成节点数,填入表3。
3.实现A*算法求解15数码问题的程序,设计两种不同的估价函数,然后重复上述1和2的实验内容,把结果填入表4。
四、实验结果与分析
(1)设置相同的初始状态和目标状态,针对不同的估价函数,求得问题的解,比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数等,填入表3。
启发函数h(n) | ||
---|---|---|
不在位数 | 不在位棋子的曼哈顿距离之和 | 0 |
初始状态 | 283064175 | 283064175 |
目标状态 | 123804765 | 123804765 |
最优解 | 第一层: 2 8 3 1 0 4 7 6 5 F= 4 G= 0 H= 4 第二层: 2 0 3 1 8 4 7 6 5 F= 5 G= 1 H= 4 第三层: 0 2 3/ 1 8 4 7 6 5 F= 6 G= 2 H= 4 第四层: 1 2 3 0 8 4 7 6 5 F= 5 G= 3 H= 2 第五层: 1 2 3 8 0 4 7 6 5 F= 4 G= 4 H= 0 | depth: 0 [2, 8, 3] [1, 0, 4] [7, 6, 5] ---------- depth: 1 [2, 0, 3] [1, 8, 4] [7, 6, 5] ---------- depth: 2 [0, 2, 3] [1, 8, 4] [7, 6, 5] ---------- depth: 3 [1, 2, 3] [0, 8, 4] [7, 6, 5] ---------- depth: 4 [1, 2, 3] [8, 0, 4] [7, 6, 5] ---------- |
扩展节点数 | 4 | 27 |
生成节点数 | 5 | 5 |
运行时间 | 114.9ms | 2.05ms |
估价函数(曼哈顿距离):
#G是深度,也就是走的步数
def fG(self):
if(self.pre!=None):
self.G=self.pre.G+1
else:
self.G=0
#H是和目标状态距离之和
def fH(self):
self.H=0
for i in range(3):
for j in range(3):
targetX=self.target[i][j]
nowP=self.findx(targetX)
#曼哈顿距离之和
self.H+=abs(nowP[0]-i)+abs(nowP[1]-j)
#F是启发函数,F=G+H
def fF(self):
self.F=self.G+self.H
运行结果如下:
2.设置与上述1相同的初始状态和目标状态,用宽度优先搜索算法(即令估计代价h(n)=0的A*算法)求得问题的解,以及搜索过程中的扩展节点数、生成节点数,填入表3。
估价函数:
# G是深度,也就是走的步数
def fG(self):
if self.pre != None:
self.G = self.pre.G + 1
else:
self.G = 0
# H是和目标状态距离之和,可以用来判断是否达到最优解
def fH(self):
self.H = 0
for i in range(3):
for j in range(3):
targetX = self.target[i][j]
nowP = self.findx(targetX)
self.H += abs(nowP[0] - i) + abs(nowP[1] - j)
宽度优先算法运行结果:
3.实现A*算法求解15数码问题的程序,设计两种不同的估价函数,然后重复上述1和2的实验内容,把结果填入表4。
15数码A*算法的估价函数(曼哈顿距离):
def cal_M_distence(cur_state):
'''
计算曼哈顿距离
:参数 state: 当前状态,4*4的列表, State.state
:返回: M_cost 每一个节点计算后的曼哈顿距离总和
'''
M_cost = 0
for i in range(4):
for j in range(4):
if cur_state[i][j] == SG[i][j]:
continue
num = cur_state[i][j]
if num == 0:
x, y = 3, 3
else:
x = num / 4 # 理论横坐标
y = num - 4 * x - 1 # 理论的纵坐标
M_cost += (abs(x - i) + abs(y - j))
return M_cost
运行结果:
15数码BFS估价函数:
def generate_child(sn_node, sg_node, hash_set, open_table):
'''
生成子节点函数
:参数 sn_node: 当前节点
:参数 sg_node: 最终状态节点
:参数 hash_set: 哈希表,用于判重
:参数 open_table: OPEN表
:返回: None
'''
if sn_node == sg_node:
heapq.heappush(open_table, sg_node)
print('已找到终止状态!')
return
for i in range(0, 4):
for j in range(0, 4):
if sn_node.state[i][j] != 0:
continue
for d in ['up', 'down', 'left', 'right']: # 四个偏移方向
x = i + MOVE[d][0]
y = j + MOVE[d][1]
if x < 0 or x >= 4 or y < 0 or y >= 4: # 越界了
continue
state = copy.deepcopy(sn_node.state) # 复制父节点的状态
state[i][j], state[x][y] = state[x][y], state[i][j] # 交换位置
h = hash(str(state)) # 哈希时要先转换成字符串
if h in hash_set: # 重复了
continue
hash_set.add(h) # 加入哈希表
# 记录扩展节点的个数
global SUM_NODE_NUM
SUM_NODE_NUM += 1
deepth = sn_node.deepth + 1 # 已经走的距离函数
node = State(deepth, state, h, sn_node) # 新建节点
sn_node.child.append(node) # 加入到孩子队列
heapq.heappush(open_table, node) # 加入到堆中
# show_block(state, deepth) # 打印每一步的搜索过程
运行结果:
启发函数h(n) | ||
---|---|---|
不在位数 | 不在位数(曼哈顿距离) | 0 |
初始状态 | [[5, 1, 2, 4], [9, 6, 3, 8], [13, 15, 10, 11], [0, 14, 7, 12]] | [[5, 1, 2, 4], [9, 6, 3, 8], [13, 15, 10, 11], [0, 14, 7, 12]] |
目标状态 | [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]] | [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]] |
最优解 | ------ 0 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 15, 10, 11] [0, 14, 7, 12] ------ 1 -------- [5, 1, 2, 4] [9, 6, 3, 8] [0, 15, 10, 11] [13, 14, 7, 12] ------ 2 -------- [5, 1, 2, 4] [0, 6, 3, 8] [9, 15, 10, 11] [13, 14, 7, 12] ------ 3 -------- [0, 1, 2, 4] [5, 6, 3, 8] [9, 15, 10, 11] [13, 14, 7, 12] ------ 4 -------- [1, 0, 2, 4] [5, 6, 3, 8] [9, 15, 10, 11] [13, 14, 7, 12] ------ 5 -------- [1, 2, 0, 4] [5, 6, 3, 8] [9, 15, 10, 11] [13, 14, 7, 12] ------ 6 -------- [1, 2, 3, 4] [5, 6, 0, 8] [9, 15, 10, 11] [13, 14, 7, 12] ------ 7 -------- [1, 2, 3, 4] [5, 0, 6, 8] [9, 15, 10, 11] [13, 14, 7, 12] ------ 8 -------- [1, 2, 3, 4] [5, 15, 6, 8] [9, 0, 10, 11] [13, 14, 7, 12] ------ 9 -------- [1, 2, 3, 4] [5, 15, 6, 8] [9, 10, 0, 11] [13, 14, 7, 12] ------ 10 -------- [1, 2, 3, 4] [5, 15, 6, 8] [9, 10, 7, 11] [13, 14, 0, 12] ------ 11 -------- [1, 2, 3, 4] [5, 15, 6, 8] [9, 10, 7, 11] [13, 0, 14, 12] ------ 12 -------- [1, 2, 3, 4] [5, 15, 6, 8] [9, 0, 7, 11] [13, 10, 14, 12] ------ 13 -------- [1, 2, 3, 4] [5, 0, 6, 8] [9, 15, 7, 11] [13, 10, 14, 12] ------ 14 -------- [1, 2, 3, 4] [5, 6, 0, 8] [9, 15, 7, 11] [13, 10, 14, 12] ------ 15 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 15, 0, 11] [13, 10, 14, 12] ------ 16 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 0, 15, 11] [13, 10, 14, 12] ------ 17 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 15, 11] [13, 0, 14, 12] ------ 18 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 15, 11] [13, 14, 0, 12] ------ 19 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 0, 11] [13, 14, 15, 12] ------ 20 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 11, 0] [13, 14, 15, 12] ------ 21 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 11, 12] [13, 14, 15, 0] | ------ 0 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 15, 10, 11] [0, 14, 7, 12] ------ 1 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 15, 10, 11] [14, 0, 7, 12] ------ 2 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 0, 10, 11] [14, 15, 7, 12] ------ 3 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 10, 0, 11] [14, 15, 7, 12] ------ 4 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 10, 7, 11] [14, 15, 0, 12] ------ 5 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 10, 7, 11] [14, 0, 15, 12] ------ 6 -------- [5, 1, 2, 4] [9, 6, 3, 8] [13, 10, 7, 11] [0, 14, 15, 12] ------ 7 -------- [5, 1, 2, 4] [9, 6, 3, 8] [0, 10, 7, 11] [13, 14, 15, 12] ------ 8 -------- [5, 1, 2, 4] [0, 6, 3, 8] [9, 10, 7, 11] [13, 14, 15, 12] ------ 9 -------- [0, 1, 2, 4] [5, 6, 3, 8] [9, 10, 7, 11] [13, 14, 15, 12] ------ 10 -------- [1, 0, 2, 4] [5, 6, 3, 8] [9, 10, 7, 11] [13, 14, 15, 12] ------ 11 -------- [1, 2, 0, 4] [5, 6, 3, 8] [9, 10, 7, 11] [13, 14, 15, 12] ------ 12 -------- [1, 2, 3, 4] [5, 6, 0, 8] [9, 10, 7, 11] [13, 14, 15, 12] ------ 13 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 0, 11] [13, 14, 15, 12] ------ 14 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 11, 0] [13, 14, 15, 12] ------ 15 -------- [1, 2, 3, 4] [5, 6, 7, 8] [9, 10, 11, 12] [13, 14, 15, 0] |
扩展节点数 | 2117 | 195811 |
生成节点数 | 21 | 15 |
运行时间 | 37.63ms | 3.49s |
五、实验总结
(1)分析不同的估价函数对A*算法性能的影响。
-
低质量估价函数:
如果h(n)的值比较低(接近于 0),则 A* 算法会表现得像广度优先搜索,这会导致大量节点被扩展,从而效率低下。
-
良好估价函数:
如果h(n)估计相对准确,使得 A* 算法能够快速接近目标,扩展的节点数会明显减少,从而提高效率。
-
高成本估价函数:
如果h(n)的值高于目标状态到节点任务的实际成本(即函数不符合可接受性),则算法仍然能找到最优解,但可能扩展更多的节点,导致计算成本上升。
(2)根据宽度优先搜索算法和A*算法求解8、15数码问题的结果,分析启发式搜索的特点。
-
A* 算法通常比 BFS 更高效,因为它优先扩展那些更有可能接近目标的节点。对于 8 数码和 15 数码问题,A* 使用合适的启发式函数(曼哈顿距离),会显著减少扩展的节点数量。
-
BFS 将需要访问大量的无用节点,尤其是在树的深度较大时,导致搜索效率低下。
-
启发式搜索的特点
启发式搜索依赖于问题的特殊性质,能够快速引导搜索。设计良好的启发式函数在很多情况下可以显著提高效率。
启发式搜索对不同问题能进行适配,可以通过不同的启发式函数适应多种情况。
通过选择具体的启发式函数,开发者可以控制算法的搜索策略,达到平衡效率和解质量的目的。
尽管启发式搜索能高效进入目标区域,但启发式函数的计算也会产生开销,尤其在状态数目庞大时,如何优化估价函数的计算成为关键问题。
(3)画出A*算法求解N数码问题的流程图。
(4)遇到的问题和解决方法
- 问题1:在设计启发函数时,如果选择的函数计算复杂度较高或者不够准确,可能导致搜索效率非常低下,甚至无法找到可行的解决方案。
解决方法:对于 8 数码问题和 15 数码问题,常见的有效启发函数包括“曼哈顿距离”和“汉明距离”。我需要评估这些启发函数的特性,选择更适合的函数。 - 问题2:在实现 A* 算法时,可能会遇到逻辑错误,特别是在优先队列的管理和节点的添加、更新方面。条件判断错误可能导致程序无法找到正确的路径。
解决方法:在实际实现过程中,我采取逐步调试的方法,使用 print 语句输出关键变量的值,包括当前节点的 f(n)、g(n) 和 h(n) 值。这些输出帮助我快速定位问题所在。
(5)总结实验心得体会。
-
通过本次实验,我深刻感受到启发式搜索在路径寻找和优化问题上的强大能力。使用适当的启发式函数可以明显提高搜索效率,尤其在面对复杂的状态空间时。
-
估价函数的合理设计直接影响到 A* 算法的性能。我的实验结果表明,不同的启发函数可以在扩展节点数和生成节点数上有显著差异,进而影响到运行时间。
Astart曼哈顿8数码
import copy
import time
class grid:
def __init__(self, stat):
self.pre = None
self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
self.stat = stat
self.find0()
self.update()
def update(self):
self.fH()
self.fG()
self.fF()
def fG(self):
if self.pre is not None:
self.G = self.pre.G + 1
else:
self.G = 0
def fH(self):
self.H = 0
for i in range(3):
for j in range(3):
targetX = self.target[i][j]
nowP = self.findx(targetX)
self.H += abs(nowP[0] - i) + abs(nowP[1] - j)
def fF(self):
self.F = self.G + self.H
def see(self):
for i in range(3):
print(self.stat[i])
print("F=", self.F, "G=", self.G, "H=", self.H)
print("-" * 10)
def seeAns(self):
ans = []
ans.append(self)
p = self.pre
while p:
ans.append(p)
p = p.pre
ans.reverse()
for i in ans:
i.see()
def findx(self, x):
for i in range(3):
if x in self.stat[i]:
j = self.stat[i].index(x)
return [i, j]
def find0(self):
self.zero = self.findx(0)
def expand(self):
i = self.zero[0]
j = self.zero[1]
gridList = []
# 检查上下左右的移动
if i > 0: # 上移
gridList.append(self.up())
if i < 2: # 下移
gridList.append(self.down())
if j > 0: # 左移
gridList.append(self.left())
if j < 2: # 右移
gridList.append(self.right())
return gridList
def move(self, row, col):
newStat = copy.deepcopy(self.stat)
# 确保不会越界
if (0 <= self.zero[0] + row < 3) and (0 <= self.zero[1] + col < 3):
# 进行移动
newStat[self.zero[0]][self.zero[1]] = newStat[self.zero[0] + row][self.zero[1] + col]
newStat[self.zero[0] + row][self.zero[1] + col] = 0
return newStat
def up(self):
return self.move(-1, 0)
def down(self):
return self.move(1, 0)
def left(self):
return self.move(0, -1)
def right(self):
return self.move(0, 1)
def isin(g, gList):
gstat = g.stat
statList = [i.stat for i in gList]
if gstat in statList:
return [True, statList.index(gstat)]
else:
return [False, 0]
def N(nums):
N = 0
for i in range(len(nums)):
if nums[i] != 0:
for j in range(i):
if nums[j] > nums[i]:
N += 1
return N
def judge(src, target):
N1 = N([num for row in src for num in row])
N2 = N([num for row in target for num in row])
return (N1 % 2) == (N2 % 2)
def Astar(startStat):
start_time = time.time()
open = []
closed = []
g = grid(startStat)
if not judge(startStat, g.target):
print("所给八数码无解,请检查输入")
exit(1)
open.append(g)
time_taken = 0
while open:
open.sort(key=lambda G: G.F)
minFStat = open[0]
if minFStat.H == 0:
print("扩展节点数:", time_taken, "最佳移动距离:", minFStat.G)
minFStat.seeAns()
break
open.pop(0)
closed.append(minFStat)
expandStats = minFStat.expand()
for stat in expandStats:
tempG = grid(stat)
tempG.pre = minFStat
tempG.update()
findstat = isin(tempG, open)
findstat2 = isin(tempG, closed)
if findstat2[0] and tempG.F < closed[findstat2[1]].F:
closed[findstat2[1]] = tempG
open.append(tempG)
time_taken += 1
if findstat[0] and tempG.F < open[findstat[1]].F:
open[findstat[1]] = tempG
time_taken += 1
if not findstat[0] and not findstat2[0]:
open.append(tempG)
time_taken += 1
end_time = time.time()
# 转换为毫秒输出
elapsed_time_ms = (end_time - start_time) * 1000
print("运行时间: {:.16f} 毫秒".format(elapsed_time_ms))
# 示例八数码初始状态
initial_stat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
Astar(initial_stat)
Astart15数码
import heapq
import copy
import time
import math
import argparse
# 初始状态
# S0 = [[11, 9, 4, 15],
# [1, 3, 0, 12],
# [7, 5, 8, 6],
# [13, 2, 10, 14]]
S0 = [[5, 1, 2, 4],
[9, 6, 3, 8],
[13, 15, 10, 11],
[0, 14, 7, 12]]
# 目标状态
SG = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
# 上下左右四个方向移动
MOVE = {'up': [1, 0],
'down': [-1, 0],
'left': [0, -1],
'right': [0, 1]}
# OPEN表
OPEN = []
# 节点的总数
SUM_NODE_NUM = 0
# 状态节点
class State(object):
def __init__(self, deepth=0, rest_dis=0.0, state=None, hash_value=None, father_node=None):
'''
初始化
:参数 deepth: 从初始节点到目前节点所经过的步数
:参数 rest_dis: 启发距离
:参数 state: 节点存储的状态 4*4的列表
:参数 hash_value: 哈希值,用于判重
:参数 father_node: 父节点指针
'''
self.deepth = deepth
self.rest_dis = rest_dis
self.fn = self.deepth + self.rest_dis
self.child = [] # 孩子节点
self.father_node = father_node # 父节点
self.state = state # 局面状态
self.hash_value = hash_value # 哈希值
def __lt__(self, other): # 用于堆的比较,返回距离最小的
return self.fn < other.fn
def __eq__(self, other): # 相等的判断
return self.hash_value == other.hash_value
def __ne__(self, other): # 不等的判断
return not self.__eq__(other)
def cal_M_distence(cur_state):
'''
计算曼哈顿距离
:参数 state: 当前状态,4*4的列表, State.state
:返回: M_cost 每一个节点计算后的曼哈顿距离总和
'''
M_cost = 0
for i in range(4):
for j in range(4):
if cur_state[i][j] == SG[i][j]:
continue
num = cur_state[i][j]
if num == 0:
x, y = 3, 3
else:
x = num / 4 # 理论横坐标
y = num - 4 * x - 1 # 理论的纵坐标
M_cost += (abs(x - i) + abs(y - j))
return M_cost
def cal_E_distence(cur_state):
'''
计算欧式距离
:参数 state: 当前状态,4*4的列表, State.state
:返回: M_cost 每一个节点计算后的曼哈顿距离总和
'''
E_cost = 0
for i in range(4):
for j in range(4):
if cur_state[i][j] == SG[i][j]:
continue
num = cur_state[i][j]
if num == 0:
x, y = 3, 3
else:
x = num / 4 # 理论横坐标
y = num - 4 * x - 1 # 理论的纵坐标
E_cost += math.sqrt((x - i)*(x - i) + (y - j)*(y - j))
return E_cost
def generate_child(sn_node, sg_node, hash_set, open_table, cal_distence):
'''
生成子节点函数
:参数 sn_node: 当前节点
:参数 sg_node: 最终状态节点
:参数 hash_set: 哈希表,用于判重
:参数 open_table: OPEN表
:参数 cal_distence: 距离函数
:返回: None
'''
if sn_node == sg_node:
heapq.heappush(open_table, sg_node)
print('已找到终止状态!')
return
for i in range(0, 4):
for j in range(0, 4):
if sn_node.state[i][j] != 0:
continue
for d in ['up', 'down', 'left', 'right']: # 四个偏移方向
x = i + MOVE[d][0]
y = j + MOVE[d][1]
if x < 0 or x >= 4 or y < 0 or y >= 4: # 越界了
continue
state = copy.deepcopy(sn_node.state) # 复制父节点的状态
state[i][j], state[x][y] = state[x][y], state[i][j] # 交换位置
h = hash(str(state)) # 哈希时要先转换成字符串
if h in hash_set: # 重复了
continue
hash_set.add(h) # 加入哈希表
# 记录扩展节点的个数
global SUM_NODE_NUM
SUM_NODE_NUM += 1
deepth = sn_node.deepth + 1 # 已经走的距离函数
rest_dis = cal_distence(state) # 启发的距离函数
node = State(deepth, rest_dis, state, h, sn_node) # 新建节点
sn_node.child.append(node) # 加入到孩子队列
heapq.heappush(open_table, node) # 加入到堆中
# show_block(state, deepth) # 打印每一步的搜索过程
def show_block(block, step):
print("------", step, "--------")
for b in block:
print(b)
def print_path(node):
'''
输出路径
:参数 node: 最终的节点
:返回: None
'''
print("最终搜索路径为:")
steps = node.deepth
stack = [] # 模拟栈
while node.father_node is not None:
stack.append(node.state)
node = node.father_node
stack.append(node.state)
step = 0
while len(stack) != 0:
t = stack.pop()
show_block(t, step)
step += 1
return steps
def A_start(start, end, distance_fn, generate_child_fn):
'''
A*算法
:参数 start: 起始状态
:参数 end: 终止状态
:参数 distance_fn: 距离函数,可以使用自定义的
:参数 generate_child_fn: 产生孩子节点的函数
:返回: 最优路径长度
'''
root = State(0, 0, start, hash(str(S0)), None) # 根节点
end_state = State(0, 0, end, hash(str(SG)), None) # 最后的节点
if root == end_state:
print("start == end !")
OPEN.append(root)
heapq.heapify(OPEN)
node_hash_set = set() # 存储节点的哈希值
node_hash_set.add(root.hash_value)
while len(OPEN) != 0:
top = heapq.heappop(OPEN)
if top == end_state: # 结束后直接输出路径
return print_path(top)
# 产生孩子节点,孩子节点加入OPEN表
generate_child_fn(sn_node=top, sg_node=end_state, hash_set=node_hash_set,
open_table=OPEN, cal_distence=distance_fn)
print("无搜索路径!") # 没有路径
return -1
if __name__ == '__main__':
time1 = time.time() # 开始计时
length = A_start(S0, SG, cal_M_distence, generate_child) # 使用曼哈顿距离
time2 = time.time() # 结束计时
if length != -1:
print("采用曼哈顿距离计算启发函数")
print("搜索最优路径长度为", length)
elapsed_time_ms = (time2 - time1) * 1000 # 转换为毫秒
print("搜索时长为 {:.2f} 毫秒".format(elapsed_time_ms)) # 格式化为小数点后两位
print("共检测节点数为", SUM_NODE_NUM) # 输出检测节点数
else:
print("无搜索路径,检测节点数为", SUM_NODE_NUM) # 当没有路径时也输出节点数
# if __name__ == '__main__':
#
# # 可配置式运行文件
# parser = argparse.ArgumentParser(description='选择距离计算方法')
# parser.add_argument('--method', '-m', help='method 选择距离计算方法(cal_E_distence or cal_M_distence)', default = 'cal_M_distence')
# args = parser.parse_args()
# method = args.method
#
# time1 = time.time()
# if method == 'cal_E_distence':
# length = A_start(S0, SG, cal_E_distence, generate_child)
# else:
# length = A_start(S0, SG, cal_M_distence, generate_child)
# time2 = time.time()
# if length != -1:
# if method == 'cal_E_distence':
# print("采用欧式距离计算启发函数")
# else:
# print("采用曼哈顿距离计算启发函数")
# print("搜索最优路径长度为", length)
# print("搜索时长为", (time2 - time1), "s")
# print("共检测节点数为", SUM_NODE_NUM)
BFS8数码
import copy
import time
# 棋盘的类,实现移动和扩展状态
class grid:
def __init__(self, stat):
self.pre = None
self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
self.stat = stat
self.find0()
self.update()
# 更新深度和距离和
def update(self):
self.fH()
self.fG()
# G是深度,也就是走的步数
def fG(self):
if self.pre != None:
self.G = self.pre.G + 1
else:
self.G = 0
# H是和目标状态距离之和,可以用来判断是否达到最优解
def fH(self):
self.H = 0
for i in range(3):
for j in range(3):
targetX = self.target[i][j]
nowP = self.findx(targetX)
self.H += abs(nowP[0] - i) + abs(nowP[1] - j)
# 查看当前状态
def see(self):
print("depth:", self.G)
for i in range(3):
print(self.stat[i])
print("-" * 10)
# 查看找到的解是如何从头移动的
def seeAns(self):
ans = []
ans.append(self)
p = self.pre
while p:
ans.append(p)
p = p.pre
ans.reverse()
for i in ans:
i.see()
# 找到数字x的位置,返回其坐标
def findx(self, x):
for i in range(3):
if(x in self.stat[i]):
j = self.stat[i].index(x)
return [i, j]
# 找到0,也就是空白格的位置
def find0(self):
self.zero = self.findx(0)
# 对当前状态进行所有可能的扩展,返回一个扩展状态的列表
def expand(self):
i = self.zero[0]
j = self.zero[1]
gridList = []
if(j == 2 or j == 1):
gridList.append(self.left())
if(i == 2 or i == 1):
gridList.append(self.up())
if(i == 0 or i == 1):
gridList.append(self.down())
if(j == 0 or j == 1):
gridList.append(self.right())
return gridList
# deepcopy多维列表的复制,防止指针赋值将原列表改变
# move只能移动行或列,即row和col必有一个为0
# 对当前状态进行移动
def move(self, row, col):
newStat = copy.deepcopy(self.stat)
tmp = self.stat[self.zero[0]+row][self.zero[1]+col]
newStat[self.zero[0]][self.zero[1]] = tmp
newStat[self.zero[0]+row][self.zero[1]+col] = 0
return newStat
def up(self):
return self.move(-1, 0)
def down(self):
return self.move(1, 0)
def left(self):
return self.move(0, -1)
def right(self):
return self.move(0, 1)
# 计算逆序数之和
def N(nums):
N = 0
for i in range(len(nums)):
if(nums[i] != 0):
for j in range(i):
if(nums[j] > nums[i]):
N += 1
return N
# 根据逆序数之和判断所给八数码是否可解
def judge(src, target):
N1 = N(src)
N2 = N(target)
return N1 % 2 == N2 % 2
# 初始化状态
startStat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
g = grid(startStat)
if not judge(startStat, g.target):
print("所给八数码无解,请检查输入")
exit(1)
visited = []
queue = [g]
time_count = 0
# 记录开始时间
start_time = time.time()
while queue:
time_count += 1
v = queue.pop(0)
# 判断是否找到解
if v.H == 0:
print("扩展节点数:", time_count, "最佳移动距离:", v.G)
# 查看找到的解是如何从头移动的
v.seeAns()
break
else:
# 对当前状态进行扩展
visited.append(v.stat)
expandStats = v.expand()
for stat in expandStats:
tmpG = grid(stat)
tmpG.pre = v
tmpG.update()
if stat not in visited:
queue.append(tmpG)
# 记录结束时间和计算运行时间
end_time = time.time()
# 计算运行时间(毫秒)
execution_time_ms = (end_time - start_time) * 1000
print("运行时间为: {:.2f} 毫秒".format(execution_time_ms))
BFS15数码
#-*-coding:utf-8-*-
import heapq
import copy
import time
# 初始状态
# S0 = [[11, 9, 4, 15],
# [1, 3, 0, 12],
# [7, 5, 8, 6],
# [13, 2, 10, 14]]
S0 = [[5, 1, 2, 4],
[9, 6, 3, 8],
[13, 15, 10, 11],
[0, 14, 7, 12]]
# 目标状态
SG = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
# 上下左右四个方向移动
MOVE = {'up': [1, 0],
'down': [-1, 0],
'left': [0, -1],
'right': [0, 1]}
# OPEN表
OPEN = []
# 节点的总数
SUM_NODE_NUM = 0
# 状态节点
class State(object):
def __init__(self, deepth=0, state=None, hash_value=None, father_node=None):
'''
初始化
:参数 deepth: 从初始节点到目前节点所经过的步数
:参数 state: 节点存储的状态 4*4的列表
:参数 hash_value: 哈希值,用于判重
:参数 father_node: 父节点指针
'''
self.deepth = deepth
self.child = [] # 孩子节点
self.father_node = father_node # 父节点
self.state = state # 局面状态
self.hash_value = hash_value # 哈希值
def __lt__(self, other): # 用于堆的比较,返回距离最小的
return self.deepth < other.deepth
def __eq__(self, other): # 相等的判断
return self.hash_value == other.hash_value
def __ne__(self, other): # 不等的判断
return not self.__eq__(other)
def generate_child(sn_node, sg_node, hash_set, open_table):
'''
生成子节点函数
:参数 sn_node: 当前节点
:参数 sg_node: 最终状态节点
:参数 hash_set: 哈希表,用于判重
:参数 open_table: OPEN表
:返回: None
'''
if sn_node == sg_node:
heapq.heappush(open_table, sg_node)
print('已找到终止状态!')
return
for i in range(0, 4):
for j in range(0, 4):
if sn_node.state[i][j] != 0:
continue
for d in ['up', 'down', 'left', 'right']: # 四个偏移方向
x = i + MOVE[d][0]
y = j + MOVE[d][1]
if x < 0 or x >= 4 or y < 0 or y >= 4: # 越界了
continue
state = copy.deepcopy(sn_node.state) # 复制父节点的状态
state[i][j], state[x][y] = state[x][y], state[i][j] # 交换位置
h = hash(str(state)) # 哈希时要先转换成字符串
if h in hash_set: # 重复了
continue
hash_set.add(h) # 加入哈希表
# 记录扩展节点的个数
global SUM_NODE_NUM
SUM_NODE_NUM += 1
deepth = sn_node.deepth + 1 # 已经走的距离函数
node = State(deepth, state, h, sn_node) # 新建节点
sn_node.child.append(node) # 加入到孩子队列
heapq.heappush(open_table, node) # 加入到堆中
# show_block(state, deepth) # 打印每一步的搜索过程
def show_block(block, step):
print("------", step, "--------")
for b in block:
print(b)
def print_path(node):
'''
输出路径
:参数 node: 最终的节点
:返回: None
'''
print("最终搜索路径为:")
steps = node.deepth
stack = [] # 模拟栈
while node.father_node is not None:
stack.append(node.state)
node = node.father_node
stack.append(node.state)
step = 0
while len(stack) != 0:
t = stack.pop()
show_block(t, step)
step += 1
return steps
def A_start(start, end, generate_child_fn):
'''
A*算法
:参数 start: 起始状态
:参数 end: 终止状态
:参数 generate_child_fn: 产生孩子节点的函数
:返回: 最优路径长度
'''
root = State(0, start, hash(str(S0)), None) # 根节点
end_state = State(0, end, hash(str(SG)), None) # 最后的节点
if root == end_state:
print("start == end !")
OPEN.append(root)
heapq.heapify(OPEN)
node_hash_set = set() # 存储节点的哈希值
node_hash_set.add(root.hash_value)
while len(OPEN) != 0:
top = heapq.heappop(OPEN)
if top == end_state: # 结束后直接输出路径
return print_path(top)
# 产生孩子节点,孩子节点加入OPEN表
generate_child_fn(sn_node=top, sg_node=end_state, hash_set=node_hash_set,
open_table=OPEN)
print("无搜索路径!") # 没有路径
return -1
if __name__ == '__main__':
time1 = time.time()
length = A_start(S0, SG, generate_child)
time2 = time.time()
if length != -1:
print("搜索最优路径长度为", length)
print("搜索时长为", (time2 - time1), "s")
print("共检测节点数为", SUM_NODE_NUM)