本文内容主要来自Artificial Intelligence A Modern Approach和Stanford CS221。
人工智能可以被建模成:学习,模型,推断。所谓的搜索往往指的是根据模型推断的过程。
本文的内容主要包括
- 搜索算法的背景和定义
- 无信息搜索算法
- 宽度优先搜索BFS
- 统一代价搜索UCS
- 深度优先搜索DFS
- 深度受限搜索DLS
- 迭代加深搜索IDS
- 双向搜索BS
- 有信息搜索算法
- 贪心搜索
- A* 搜索
- 迭代最佳优先搜索RBFS
- 启发式设计
搜索算法的背景和定义
首先,搜索算法可以分为树搜索和图搜索。
简单来说,树搜索算法可能会导致状态的重复,所以在图上往往使用图搜索,当然如果你可以保证你的算法不会遇到重复状态的话,也可以放心的使用树搜索。
树搜索:

图搜索:

容易看出,图搜索比树搜索多维护了一个explored
队列,这个队列用来记录算法经过的节点,通过检查新的节点是否在这个队列中,图搜索避免了重复。
而两者都有的frontier
队列,则是用来记录将要探索的节点。
算法开始的时候把初始节点放到 frontier
里,接下来一直探索,如果frontier
空了却没有找到最终的目标,说明目标是不存在的,算法返回failure。
其次是对节点的描述,一个节点包括:

其中关于STATE,CS221给出了一个很好的解释:
A state is a summary of all the past actions sufficient to choose future actions optimally.
最后是描述算法好坏的指标:
- Completeness: Is the algorithm guaranteed to find a solution when there is one?
- Optimality: Does the strategy find the optimal solution?
- Time complexity: How long does it take to find a solution?
- Space complexity: How much memory is needed to perform the search?
为了描述后两个性能指标,我们在图搜索上定义一些常数(这些常数由问题确定,与算法无关)
- branching factor b:一个节点的最多分支
- maximum depth m:整个图的最大深度
- solution depth d:最优解所在的深度
无信息搜索算法
宽度优先搜索BFS

说白了BFS就是用一个FIFO的队列维护frontier
中的节点。
我们用BFS的目标是找到目标节点,但是这个从初始节点通向目标节点的路径可能有很多条。
很多时候,我们不仅希望找到节点,还希望在搜索的过程中最小化某种指标,这个指标可以用
如果满足对任意
这个证明是很直观的,只需要一层层的向下找就可以了。往往这样假设的
注意,如果
因为目标节点在d层,所以BFS的时间复杂度和空间复杂度都是
统一代价搜索UCS
接下来,UCS就是来解决
实际上UCS也不复杂,学过了Dijkstra算法就能发现,UCS其实就是Dijkstra算法。

UCS和BFS的主要区别是UCS维护了一个优先队列,通过这个优先队列保证达到目标节点的时候路径一定是最短的。
这个也好理解,因为从frontier
取出的节点满足chooses the lowest-cost node in frontier
,所以如果这个节点是目标节点,那么即便是存在另一个路径也可以通向目标节点,因为当前走过的路径已经比这条路长了,又有
实际上,一个更直观的事实是,任何被放入explored
的节点都满足最短路径。
深度优先搜索DFS
DFS和BFS的区别就是DFS用一个LIFO的队列(其实就是栈)来维护frontier
。
注意,因为DFS一旦找到了目标节点就会停止,所以当
但是,如果

值得一提的是,在树搜索算法中,DFS非常容易陷入无穷的循环,所以DFS往往用于图搜索。
因为DFS理论上要搜完整个空间才知道是否有解,所以最大的时间复杂度是
深度受限搜索DLS
DLS规定了DFS的最大搜索深度,对于一个给定的常数l,DLS在到达这个深度之后就会放弃搜索,转而去探索frontier
中的其他节点。

注意DLS是一个递归算法,而之前的算法都是循环的。
函数RECURSIVE-DLS可以返回三种值,除了之前的solution和failure,还有递归算法的内部状态cutoff。
对于每一个节点,算法会递归的展开它的子节点,如果有一个节点返回solution,算法会将这个solution向上传递至跟节点。
如果其中一个节点返回cutoff,算法认为到达了受限深度,向上传递cutoff。
如果这个节点的所有子节点都不返回solution或者cutoff,也就是说所有节点都返回failure,则向上传递failure。
如果最后的跟节点的输出是failure,说明DLS的受限不起作用,也就是说这个图最深的深度也没有受限的深度深。
如果最后的跟节点的输出是cutoff,则说明虽然当前深度内并没有找到目标节点,但是不保证更深的节点中没有目标节点。
DLS和DFS的复杂度很像,时间和空间复杂度分别是
迭代加深搜索IDS
IDS 就是在DLS的基础上,不断的将常数l扩大,从而增加搜索深度。


IDS弥补了深度受限搜索的缺点,它允许算法不断的增加深度来寻找目标节点。
它和BFS看起来很像,而且IDS似乎在实现上产生了很大的重复,实际上IDS比BFS的空间复杂度小的多,但是时间复杂度相对大一些。
IDS的时间和空间复杂度分别是
双向搜索BS
BS的假设是我们不仅可以从初始节点出发,同时还可以从目标节点出发,从而实现双向的搜索。
BS的内部往往是由BFS实现的,如果从初始节点和从目标节点的搜索都是BFS,可以保证算法的最优。
实际上,BS就是将BFS画的半径为d的一个大圆,换成了半径为d/2的两个小圆。

因为BS是基于BFS实现的,所以时间复杂度和空间复杂度都很像,都是
最后比较一些这些算法

注意,这里DFS不完备是因为可能陷入无穷循环,不满足最优性是因为默认
有信息搜索算法
所谓的有信息搜索算法又称启发式搜索,往往假定了我们对搜索问题还有额外的知识。
启发式搜索根据evaluation function
我们可以定义
同时,我们有一个函数heuristic function
h(n) = estimated cost of the cheapest path from the state at node n to a goal state.
我们认为对于所有的
贪心搜索Greedy best-first search
我们假定在下面的地图上搜索,初始节点是Arad,目标节点是Bucharest。

贪心搜索即令
我们假设

那么搜索的过程就是

注意,贪心搜索不能保证最优,甚至都不能保证完备,因为它可能陷入“死胡同”,例如从Iasi到Fagaras,它会选择去Neamt,然后就卡死了。
A* 搜索
A* search的evaluation function
同时,当
admissible heuristic指的是
An admissible heuristic is one that never overestimates the cost to reach the goal.
显然,在我们的例子中,因为两点之间直线最短,所以admissible是满足的。
consistent heuristic是指
for every nodeand every successor
of
generated by any action a, the estimated cost of reaching the goal from
is no greater than the step cost of getting to
plus the estimated cost of reaching the goal from
![]()
也就是满足
其实,算上之前要求的对于目标节点满足
而在CS221中只给了第1个和第3个条件,实际上,我们可以从1,3条件推出2,也可以从2,3条件推出1。
如果有
如果有2,3条件,显然有
接下来,只要把UCS的

接下里,更重要的是,我们要证明A*算法的最优性。
首先不难看出
根据和UCS类似的推导,我们可以说明一旦任意节点
explored
,它的路径都满足
这看似没什么用,因为
但是目标节点不同,因为
A*算法满足了最优性,这个性质UCS也可以满足,那么我们为什么不用UCS而是用A*呢?
直观的理解,UCS是像圆一样从一个点蔓延,直到找到了目标节点;而A*则是朝着目标节点的方向蔓延,尽管做不到只走一条路径,但也高效的多。
我们可以用等高线的方式更好的理解这个性质:

图中的等高线是
进一步讲,我们知道
- A expands all nodes with f(n) < C*
- A* might then expand some of the nodes right on the “goal contour” (where f(n) = C*) before selecting a goal node.
从上面的树状图可以看出,这个问题的C*是418,而
所以A*的一个问题就是要探索完所有
但无论如何,A*都要比UCS好,因为UCS要探索完所有
迭代最佳优先搜索RBFS

(算法中有一些max和min,在我看来似乎没有必要)
RBFS看起来很有意思,因为它既是一个递归算法,又在递归的内部进行了循环。
它维护了一个successor
队列, 根据这个successor
队列计算出了两个值,分别是best和alternative,best是successor
中最小的
successor
中
第二小的
然后,算法选择扩展最小的
successor
中的节点的
同时它会将这个节点(之前best的节点)的
在之前的例子中,RBFS的探索过程如下:

RBFS相比A*节省了大量的空间,实际上,它的空间复杂度和节点的数目是线性的。但是因为它会在递归的过程中“遗忘”因此时间复杂度更高。
启发式设计
通过刚才A*和RBFS的例子可以看出来,一个好的
那么,如何设计更好的
一个经典的例子是8-puzzle

目标是从Start State挪到Goal State。
在8-puzzle中,我们可以看到搜索树的宽度最小为2(空格在四角),最大为4(空格在中间),总的来说平均为3。
8-puzzle问题的平均移动次数是22次,需要探索
但是,即便剪枝,15-puzzle对应的数目依旧达到了
有两种启发式值得参考:

显然,对于所有的
我们定义一个用来评价heuristic好坏的指标effective branching factor b*,它的定义为
其中
其实,直觉上,我们可以用
但是因为初始节点和目标节点的不同,椭圆的大小是不好确定的,这时候只统计

可以看到
这里有一个更一般的结论,因为
生成heuristic主要有三个方法,分别是
- Generating admissible heuristics from relaxed problems
- Generating admissible heuristics from subproblems: Pattern databases
- Learning heuristics from experience
注意第三个会用到一些机器学习的方法,但是并不能保证heuristic是admissible和consistent的。
此外,如果我们有多个heuristic都满足admissible和consistent,我们可以得到新的heuristic
可以证明,这个新的heuristic性能更好,且满足admissible和consistent。
- 性能好:因为
- admissible: 每个都满足
,
自然满足
- consistent:即证
,其中
,
,若
结论是显然的;若
有