我在Python中实现了一个版本的Rush Hour(拼图游戏)作为一些AI算法的演示。游戏并不重要,因为AI相对独立于其细节:我试图在Python中实现迭代加深深度优先搜索的版本,如下所示(请注意,此代码几乎直接从Russell和Norvig的AI文本中复制) ,第3版,对于那些了解它的人):
def depth_limited_search(game, limit):
node = GameNode(game)
frontier = []
#frontier = Queue.Queue()
frontier.append(node)
#frontier.put(node)
frontier_hash_set = set()
frontier_hash_set.add(str(node.state))
explored = set()
cutoff = False
while True:
if len(frontier) == 0:
#if frontier.empty():
break
node = frontier.pop()
#node = frontier.get()
frontier_hash_set.remove(str(node.state))
explored.add(str(node.state))
if node.cost > limit:
cutoff = True
else:
for action in node.state.get_actions():
child = node.result(action)
if str(child.state) not in frontier_hash_set and str(child.state) not in explored:
if child.goal_test():
show_solution(child)
return child.cost
frontier.append(child)
#frontier.put(child)
frontier_hash_set.add(str(child.state))
if cutoff:
return 'Cutoff'
else:
return None
def iterative_deepening_search(game):
depth = 0
while True:
result = depth_limited_search(game, depth)
if result != 'Cutoff':
return result
depth += 1
实施的搜索算法确实在合理的时间内返回答案。问题是答案是非最佳的。我选择的测试板在8次移动中具有最佳答案,但是该算法使用10次移动返回一个。如果我用注释行替换注释掉的线以上的线,有效地将迭代加深深度优先搜索转换为迭代加深的广度优先搜索,算法将返回最佳答案!
我一直在盯着这个很长一段时间,试图找出遍历顺序的简单变化如何导致非最优性,我无法弄明白。任何指出我的愚蠢错误的帮助都将非常感激
答案 0 :(得分:2)
我无法测试这个,但我认为问题在于这个谓词:
if str(child.state) not in frontier_hash_set and \
str(child.state) not in explored:
问题在于,在此DFS迭代的早期,child.state
可能已插入到边界或一组访问状态,但成本更高。
S -> A -> B -> C -> G
\ /
\-----> D -/
显然你不会达到限制<的目标。 3.但是当limit = 3时,你的DFS可能首先访问A,B和C.然后当它回溯到S,访问D并尝试访问C时,它不会因为你之前访问它而将C推入堆栈。
要解决这个问题,我相信你需要在回溯时“取消访问”状态。在实现方面,以递归方式编写算法并以功能样式传递已探索状态集的副本可能是最简单的。
答案 1 :(得分:1)
您的代码找到次优解决方案的原因仅仅是因为深度优先搜索和面包优先搜索工作的方式。
广度优先搜索会在尝试任何 9-move解决方案之前尝试所有可能的8-move解决方案,因此广度优先搜索必须找到一个尽可能少动作的解决方案。
相比之下,深度优先搜索将尝试使用所有可能的8移动解决方案之前的9,10,11 ......移动解决方案,因此可能会产生次优结果。
例如,给定一个如下所示的游戏树:
1
/ \
2 3
/ \ / \
4 5 6 7
/\ /\ /\ /\
8 9 A B C D E F
给定的代码将按顺序在节点上调用goal_test
:2,3,6,7,E,F,C,D,4,5,A,B,8,9。注意节点E和F在节点6的子节点之前以及节点2的子节点之前进行测试。这是深度优先搜索。
您的代码的替代版本将按此顺序调用goal_test
:2,3,4,5,6,7,8,9,A,B,C,D,E,F。这是面包优先搜索。
修改:我的不好,我对上述goal_test
的调用顺序仅适用于iterative_deepening_search
中的最后一次迭代。拨打goal_test
的实际顺序是2,3,2,3,6,7,4,5,2,3,6,7,E,F,C,D,4,5,A, B,8,9。我通过实际运行代码验证了这一点,所以我100%确定它现在是正确的。
您确定每个游戏节点的child.state
值是唯一的吗?如果不是,则算法将失败。例如,考虑如果节点4具有与节点6相同的状态会发生什么。在这种情况下,您的代码将按以下顺序在节点上调用goal_test
:2,3,2,3,6,7,5,2 ,3,6,7,E,F,C,D,5,A,B。请注意goal_test
从不在节点4,8和9上调用。
但是,如果我们切换到代码的备用版本,则按此顺序调用goal_test
:2,3,2,3,4,5,7,2,3,4,5,7, 8,9,A,B,E,F。现在goal_test
未在节点6,C和D上调用!我相信这可能是你问题的原因。