您可以使用 a set
(在数学意义上,即不能包含重复项的集合)来存储您已经看到的状态。您需要能够对此执行的操作是:
几乎每种编程语言都应该已经支持可以在常量中执行这两种操作的数据结构(O(1)) 时间。例如:
乍一看,将你见过的所有状态添加到这样的集合中似乎在内存方面会很昂贵,但与你的边界已经需要的内存相比,它并不算太糟糕;如果您的分支因子是b, 你的边界将增长b−1您访问的每个节点的元素(删除1从边界节点“访问”它,添加b新的继任者/孩子),而您的集合只会增长1每个访问节点的额外节点。
在伪代码中,这样的集合(让我们命名它closed_set
,以与维基百科上的伪代码保持一致,可以在广度优先搜索中使用,如下所示:
frontier = First-In-First-Out Queue
frontier.add(initial_state)
closed_set = set()
while frontier not empty:
current = frontier.remove_next()
if current == goal_state:
return something
for each child in current.generate_children()
if child not in closed_set: // This operation should be supported in O(1) time regardless of closed_set's current size
frontier.add(child)
closed_set.add(current) // this should also run in O(1) time
(此伪代码的某些变体也可能起作用,并且根据情况或多或少地有效;例如,您还可以使用closed_set
包含已将子节点添加到边界的所有节点,然后完全避免generate_children()
调用如果current
已经在closed_set
.)
我上面描述的将是处理这个问题的标准方法。直觉上,我怀疑一个不同的“解决方案”可能是在将新的继承状态列表添加到边界之前总是随机化它们的顺序。这样,您不会避免偶尔添加您之前已经扩展到边界的状态的问题,但我确实认为它应该会显着降低陷入无限循环的风险。
请注意:我不知道对此解决方案的任何正式分析证明它总是避免无限循环。如果我尝试在脑海中“运行”这个,直觉上,我怀疑它应该可以工作,并且不需要任何额外的内存。虽然可能存在我现在没有想到的边缘情况,所以它也可能根本不起作用,上面描述的标准解决方案将是一个更安全的选择(以更多内存为代价)。