估价函数对算法性能的影响
曼哈顿距离:曼哈顿距离作为一种启发式函数,在搜索算法中被广泛使用。因为它能更加准确地评估当前状态与目标状态的差异。具体来说,它考虑了状态中所有数字与目标状态之间的距离,即在欧几里德空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和。曼哈顿距离是指两点在网格状的坐标系上的横纵向距离之和。在八数码问题中,每个数字在目标状态需要移动的距离即为曼哈顿距离,因此曼哈顿距离可以很好地估计代价函数。但是,当状态空间很大时,曼哈顿距离可能会导致搜索效率低下。
错位数:错位数则是指当前状态与目标状态不同的数字个数。当状态空间较小时,错位数可以很好地估计代价函数。但是,当状态空间很大时,错位数可能会导致估计代价函数相差无几,从而导致搜索效率低下。
流程图
代码实现
import time
import heapq
goal_state = [0, 1, 2, 3, 4, 5, 6, 7, 8] # 目标状态
# 定义状态节点类
class Node:
def __init__(self, state, parent, g, h):
self.state = state # 节点状态
self.parent = parent # 父节点
self.g = g # 到达此节点所需步数
self.h = h # 启发式函数
# 计算f值
def f(self):
return self.g + self.h
# 定义小于运算符用于堆排序
def __lt__(self, other):
return self.f() < other.f()
# 获取启发式函数h1值(曼哈顿距离)
def get_h1(state):
h = 0
for i in range(len(state)):
if state[i] == 0:
continue
h += abs(i // 3 - state[i] // 3) + abs(i % 3 - state[i] % 3)
return h
# 获取启发式函数h2值(错位数)
def get_h2(state):
h = 0
for i in range(len(state)):
if state[i] == 0:
continue
if state[i] != goal_state[i]:
h += 1
return h
# 获取一个节点的后继状态列表
def get_successors(node):
successors = []
zero_index = node.state.index(0)
if zero_index in [3, 4, 5, 6, 7, 8]:
new_state = node.state.copy()
new_state[zero_index], new_state[zero_index - 3] = new_state[zero_index - 3], new_state[zero_index]
new_node = Node(new_state, node, node.g + 1, get_h1(new_state))
successors.append(new_node)
if zero_index in [1, 2, 4, 5, 7, 8]:
new_state = node.state.copy()
new_state[zero_index], new_state[zero_index - 1] = new_state[zero_index - 1], new_state[zero_index]
new_node = Node(new_state, node, node.g + 1, get_h1(new_state))
successors.append(new_node)
if zero_index in [0, 1, 3, 4, 6, 7]:
new_state = node.state.copy()
new_state[zero_index], new_state[zero_index + 1] = new_state[zero_index + 1], new_state[zero_index]
new_node = Node(new_state, node, node.g + 1, get_h1(new_state))
successors.append(new_node)
if zero_index in [0, 3, 6, 1, 4, 7]:
new_state = node.state.copy()
new_state[zero_index], new_state[zero_index + 3] = new_state[zero_index + 3], new_state[zero_index]
new_node = Node(new_state, node, node.g + 1, get_h1(new_state))
successors.append(new_node)
return successors
# A*算法求解八数码问题
def astar_search(start_state, h):
start_node = Node(start_state, None, 0, h(start_state))
pq = []
heapq.heappush(pq, (start_node.f(), start_node))
visited = set()
nodes_generated = 0
while pq:
node = heapq.heappop(pq)[1]
if node.state == goal_state:
path = []
while node:
path.append(node.state)
node = node.parent
return path[::-1], nodes_generated
if str(node.state) in visited:
continue
visited.add(str(node.state))
successors = get_successors(node)
nodes_generated += len(successors)
for successor in successors:
if str(successor.state) in visited:
continue
heapq.heappush(pq, (successor.f(), successor))
return None, nodes_generated
if __name__ == '__main__':
start_state = [1, 2, 5, 3, 4, 0, 6, 7, 8] # 初始状态
print('初始状态: ', start_state)
# 使用启发式函数h1
start_time = time.time()
path, nodes_generated = astar_search(start_state, get_h1)
end_time = time.time()
print('使用启发式函数h1:')
print('路径: ', path)
print('生成节点数: ', nodes_generated)
print('运行时间: ', end_time - start_time)
# 使用启发式函数h2
start_time = time.time()
path, nodes_generated = astar_search(start_state, get_h2)
end_time = time.time()
print('使用启发式函数h2:')
print('路径: ', path)
print('生成节点数: ', nodes_generated)
print('运行时间: ', end_time - start_time)
运行结果:
结果分析:
这个程序实现了解决八数码游戏问题的A*算法。该程序定义了节点类Node,包含节点状态、父节点、到达此节点所需步数和启发式函数h。两种启发式函数分别为曼哈顿距离h1和错位数h2。通过计算每个节点的f值并使用堆排序进行搜索,找到目标状态后返回路径。在这个程序中用初始状态为[1, 2, 5, 3, 4, 0, 6, 7, 8]进行测试,使用h1和h2两种启发式函数分别运行A*算法求解八数码问题。结果显示,两种启发式函数均可找到最短路径,并且生成节点数相同。但是,使用h1的运行时间为0.001秒左右,而使用h2的运行时间是0秒,这是由于h1比h2更准确,但是计算代价更高。
从实验结果来看,两种启发式函数得到了相同的解,且在这个问题上生成的节点数也相同。但是,从运行时间的角度来看,曼哈顿距离的搜索耗时为0.0009秒,而错位数的搜索则为0秒。这意味着使用错位数作为启发式函数可以更快地完成搜索。