在 Java 中使用启发式搜索更快地解决问题文档格式.docx
- 文档编号:14370610
- 上传时间:2022-10-22
- 格式:DOCX
- 页数:10
- 大小:26.71KB
在 Java 中使用启发式搜索更快地解决问题文档格式.docx
《在 Java 中使用启发式搜索更快地解决问题文档格式.docx》由会员分享,可在线阅读,更多相关《在 Java 中使用启发式搜索更快地解决问题文档格式.docx(10页珍藏版)》请在冰豆网上搜索。
角色做出的每个动作都与图形中的一个边缘相对应,而且角色的目标是找到最短路径,与对手角色交手。
深度优先搜索和广度优先搜索等算法是流行的图形遍历算法。
但它们被视为非启发式算法,而且常常受到它们可以解决的问题规模的严格限制。
此外,不能保证深度优先搜索能找到最优解决方案(或某些情况下的任何解决方案),可以保证广度优先搜索仅能在特殊情况下找到最优解决方案。
相比之下,启发式搜索是一种提示性搜索,利用有关一个问题的知识,以启发式方式进行编码,从而更高效地解决问题。
启发式搜索可以解决非启发式算法无法解决的很多难题。
视频游戏寻路是启发式搜索的一个受欢迎的领域,它还可以解决更复杂的问题。
2007年举行的无人驾驶汽车比赛“DARPA城市挑战赛”的优胜者就利用了启发式搜索来规划平坦的、直接的可行使路线。
启发式搜索在自然语言处理中也有成功应用,它被用于语音识别中的文本和堆栈解码句法解析。
它在机器人学和生物信息学领域也有应用。
与传统的动态编程方法相比较,使用启发式搜索可以使用更少的内存更快地解决多序列比对(MultipleSequenceAlignment,MSA),这是一个经过深入研究的信息学问题。
通过Java实现启发式搜索
Java编程语言不是实现启发式搜索的一种受欢迎的选择,因为它对内存和计算资源的要求很高。
出于性能原因,C/C++通常是首选语言。
我们将证明Java是实现启发式搜索的一种合适的编程语言。
我们首先表明,在解决受欢迎的基准问题集时,A*的textbook实现确实很缓慢,并且会耗尽可用内存。
我们通过重访一些关键实现细节和利用替代的JCF来解决这些性能问题。
很多这方面的工作都是本文作者合著的一篇学术论文中发表的作品的一个扩展。
尽管原作专注于C/C++编程,但在这里,我们展示了适用于Java的许多同样的概念。
广度优先搜索
实现启发式搜索的细节。
我们将使用广度优先搜索的一个以代理为中心的视图。
在一个以代理为中心的视图中,代理据说处于某种状态,并且可从该状态获取一组适用的操作。
应用操作可将代理从其当前状态转换到一个新的后继状态。
该视图适用于多种类型的问题。
广度优先搜索的目标是设计一系列操作,将代理从其初始状态引导至一个目标状态。
从初始状态开始,广度优先搜索首先访问最近生成的状态。
所有适用的操作在每个访问状态都可以得到应用,生成新的状态,然后该状态被添加到未访问状态列表(也称为搜索的前沿)。
访问状态并生成所有后继状态的过程被称为扩展该状态。
您可以将该搜索过程看作是生成了一个树:
树的根节点表示初始状态,子节点由边缘连接,该边缘表示用于生成它们的操作。
图1显示该搜索树的一个图解。
白圈表示搜索前沿的节点。
灰圈表示已展开的节点。
图1.
二叉树上的广度优先搜索顺序
搜索树中的每一个节点表示某种状态,但两个独特的节点可表示同一状态。
例如,搜索树中处于不同深度的一个节点可以与树中较高层的另一个节点具有同样的状态。
这些重复节点表示在搜索问题中达到同一状态的两种不同方式。
重复节点可能存在问题,因此必须记住所有受访节点。
清单1显示广度优先搜索的伪代码:
清单1.广度优先搜索的伪代码
在清单1中,我们将搜索前沿保留在一个open列表(第2行)中。
将访问过的节点保留在closed列表(第3行)中。
closed列表有助于确保我们不会多次重访任何节点,从而不会重复搜索工作。
仅当一个节点不在closed列表中时才能将其添加到前沿。
搜索循环持续至open列表为空或找到目标为止。
在图1中,您可能已经注意到,在移至下一层之前,广度优先搜索会访问搜索树的每个深度层的所有节点。
在所有操作具有相同成本的问题中,搜素树中的所有边缘具有相同的权重,这样可保证广度优先搜索能找到最优解决方案。
也就是说,生成的第一个目标在从初始状态开始的最短路径上。
在某些域中,每个操作有不同的成本。
对于这些域,搜索树中的边缘具有不统一的权重。
在这种情况下,一个解决方案的成本是从根到目标的路径上所有边缘权重的总和。
对于这些域,无法保证广度优先搜索能找到最优解决方案。
此外,广度优先搜索必须展开树的每个深度层的所有节点,直至生成目标。
存储这些深度层所需的内存可能会快速超过最现代的计算机上的可用内存。
这将广度优先搜索限制于很窄的几个小问题。
Dijkstra的算法是广度优先搜索的一个扩展,它根据从初始状态到达节点的成本对搜索前沿上的节点进行排序(排列open列表)。
不管操作成本是否统一(假设成本是非负值),它都确保可以找到最优解决方案。
然而,它必须访问成本少于最优解决方案的所有节点,因此它被限制于解决较少的问题。
下一节将描述一个能解决大量问题的算法,该算法能大幅减少查找最优解决方案所需访问的节点数量。
A*搜索算法
A*算法或其变体是最广为使用的启发式搜索算法之一。
可以将A*看作是Dijkstra的算法的一个扩展,它利用与一个问题有关的知识来减少查找一个解决方案所需的计算数量,同时仍然保证最优的解决方案。
A*和Dijkstra的算法是最佳优先图形遍历算法的典型示例。
它们是最佳优先算法,是因为他们首先访问最佳的节点,即出现在通往目标的最短路径上的那些节点,直至找到一个解决方案。
对于许多问题,找到最佳解决方案至关重要,这是让A*这样的算法如此重要的原因。
A*与其他图形遍历算法的不同之处在于使用了启发式估值。
启发式估值是有关一个问题的一些知识(经验法则),该知识能让您做出更好的决策。
在搜索算法的上下文中,启发式估值有具体的含义:
估算从特定节点到一个目标的成本的一个函数。
A*可以利用启发式估值来决定哪些节点是最应该访问的,从而避免不必要的计算。
A*尝试避免访问图形中几乎不通向最优解决方案的节点,通常可用比非启发式算法更少的内存快速找到解决方案。
A*确定最应该访问哪些节点的方式是为每个节点计算一个值(我们将其称为f值),并根据该值对open列表进行排序。
f值是使用另外两个值计算出来的,即节点的g值和h值。
一个节点的g值是从初始状态到达一个节点所需的所有操作的总成本。
从节点到目标的估算成本是其h值。
这一估算值是启发式搜索中的启发式估值。
f值最小的节点是最应该访问的节点。
图2展示该搜索过程:
图2.基于f值的A*搜索顺序
在图2的示例中,前沿有三个节点。
有两个节点的f值是5,一个节点的f值是3。
接下来展开f值最小的节点,该节点直接通往一个目标。
这样一来A*就无需访问其他两个节点下的任何子树,如图3所示。
这使得A*比广度优先搜索等算法要高效得多。
图3.A*不必访问f
值较高的节点下的子树
如果A*使用的启发式估值是可接受的,那么A*仅访问找到最优解决方案所需的节点。
为此A*很受欢迎。
没有其他算法能用可接受的启发式估值,通过访问比A*更少的节点保证找到一个最优解决方案。
要让启发式估算成为可接受的,它必须是一个下限值:
一个小于或等于到达目标的成本的值。
如果启发满足另一个属性,即一致性,那么将首次通过最优路径生成每个状态,而且该算法可以更高效地处理重复节点。
与上一节的广度优先搜索一样,A*维护两个数据结构。
已生成但尚未访问的节点存储在一
个open列表中,而且访问的所有标准节点都存储在一个closed列表中。
这些数据结构的实现以及使用它们的方式对性能有很大的影响。
我们将在后面的一节中对此进行详细探讨。
清单2显示textbookA*搜索的完整伪代码。
清单2.A*搜索的伪代码
在清单2中,A*从open列表中的初始节点入手。
在每次循环迭代中,open列表上的最佳节点被删除。
接下来,open上最佳节点的所有适用操作被应用,生成所有可能的后继节点。
对于每个后继节点,我们将通过检查确认它表示的状态是否已被访问。
如果没有,则将其添加到open列表。
如果它已经被访问,则需要通过一个更好的路径确定我们是否达到了这一状态。
如果是,则需要将该节点放在open列表上,并删除次优的节点。
我们可以使用有关要解决的问题的两个假设简化这一段伪代码:
我们假设所有操作有相同的成本,而且我们有可接受的、一致的启发式估值。
因为启发式估值是一致的,且域中的所有操作具有相同的成本,那么我们永远无法通过一个更好的路径重访一个状态。
结果还表明,对于一些域,在open列表中放置重复节点比每次生成新节点时检查是否有重复节点更高效。
因此,我们可以通过将所有新后继节点附加到open列表来简化实现,不管它们是否已经被访问。
我们通过将清单2中的最后四行组合为一行来简化伪代码。
我们仍然需要避免循环,因此在展开一个节点之前,必须检查是否有重复节点。
我们可以省略掉IMPROVE函数的细节,因为在简化版本中不再需要它。
清单3显示简化的伪代码:
清单3.A*搜索的简化伪代码
A*的Javatextbook实现
本节我们将介绍如何基于清单3中简化的伪代码完成A*的Javatextbook实现。
您会看到,这一实现无法解决30GB内存限制下的一个标准启发式搜索基准。
我们希望我们的实现尽量大众化,因此我们首先定义了一些接口来提取A*要解决的问题。
我们想通过A*解决的任何问题都必须实现Domain接口。
Domain接口提供具有以下用途的方法:
查询初始状态查询一个状态的适用操作计算一个状态的启发式估值生成后继状态
清单4显示了Domain接口的完整代码:
清单4.Domain接口的Java源代码
A*搜索为搜索树生成边缘和节点对象,因此我们需要Edge和Node类。
每个节点包含4个字段:
节点表示的状态、对父节点的引用,以及节点的g和h值。
清单5显示Node类的完整代码:
清单5.Node类的Java源代码
每个边缘有三个字段:
边缘的成本或权重、用于为边缘生成后继节点的操作,以及用于为边缘生成父节点的操作。
清单6显示了Edge类的完整代码:
清单6.Edge类的Java源代码
A*算法本身会实现SearchAlgorithm接口,而且仅需要Domain和Edge接口
。
SearchAlgorithm接口仅提供一个方法来执行具有指定初始状态的搜索。
search()方法返回SearchResult的一个实例。
SearchResult类提供搜索统计。
SearchAlgorithm接口的定义如清单7所示:
清单7.SearchAlgorithm接口的Java源代码
用
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 中使用启发式搜索更快地解决问题 使用 启发式 搜索 解决问题