人智导 搜索

搜索问题

盲目搜索过于困难的时候,采用一些方法来帮助我们加快搜索进程

深度优先

优先扩展深度更深的节点,略

性质:

  • 一般不能保证最优解
  • 深度限制不合理时可能找不到解,并且可能退化为穷举
  • 是一个通用的算法,并且相对节省内存

宽度优先

优先扩展深度更浅的节点,略

性质:

  • 问题有解的时候一定能找到,如果问题是单位耗散值则一定能找到最优解
  • 通用算法,但是效率较低,存储量较大

Dijkstra

优劣:

  • 有解一定可以找到最佳解
  • 只考虑了距离起点的具体

启发式图搜索

引入启发知识,用于评估节点到达目标的距离,尽可能减少搜索范围

A算法

评价函数为:

f(n)=g(n)+h(n)f(n) = g(n) + h(n)

其中g(n)g(n)是点nn到起点ss的耗散值估计值,h(n)h(n)是点nn到目标tt的耗散值估计值,真实值分别为g(n)g^{*}(n)h(n)h^{*}(n)

AA算法伪代码如下,主要为维护CLOSE\text{CLOSE}表与OPEN\text{OPEN}表:

CLOSE=(),OPEN=(s)\text{CLOSE} = (),\, \text{OPEN} = (s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while OPEN is not empty, do:
n = OPEN.first()
if n == g
return n
OPEN.pop(n)
CLOSE.push(n)
expand(n)
for child in n.childs()
if child in OPEN:
# 标记为m_{k}
f(child) = min{f(child), f(n) + d(n, child) + h(child)}
else if child in CLOSE:
# 标记为m_{l}
f(child) = min{f(child), f(n) + d(n, child) + h(child)}
### important!
OPEN.push(child) if f(child) change
else:
# 标记为m_{j}
f(child) = f(n) + d(n, child) + h(child)
OPEN.push(child)
sort(OPEN, f)

最终从目标开始顺次访问父节点即可

A*算法

AA算法中的启发函数满足条件h(n)h(n)h(n)\leq h^{*}(n),则得到AA^{*}算法

放宽了约束条件得到估计值,AA^{*}算法的优势在与:

  • 一定能找到最优解
  • 启发信息越少的算法,所拓展的节点越多(不少于信息更多的算法)

对启发函数的评价方式为平均分叉数bb^{*},探索dd层后其与总搜索节点数NN的关系为:

N=i=0dbd=1b(d+1)1bN = \sum\limits_{i=0}^{d}b^{*d} = \frac{1 - b^{*(d+1)}}{1 - b^{*}}

一般情况下,bb^{*}是与问题相关,而与问题的规模关系不大

改进的A*算法

对于AA^{*}算法,其最大的问题就是CLOSE表中的节点可能再次进入OPEN表中,也即没有在第一次探索到节点的时候就找到其最短路径,会造成重复探索,导致资源的浪费。因此我们需要对启发函数做出如下限制:

若启发函数hh满足,对于节点vv与其父节点uu有:

h(u)h(v)d(u,v)h(u) - h(v) \leq d(u, v)

并且有h(t)=0h(t) = 0,则称hh是单调的

hh是单调的,那么扩展了节点nn之后就已经找到了到达nn的最优路径,并且单调的hh一定满足AA^{*}条件

改进后有:

  • 扩展的节点一定满足f(n)f(s)f(n) \leq f^{*}(s)
  • OPEN表中满足f(n)<f(s)f(n) < f^{*}(s)的一定会被扩展

再次改进,使用当前被扩展的最大节点来估计f(s)f^{*}(s),即max(f(n))f(s)\max(f(n)) \sim f^{*}(s),具体来说:

维护一个NEST表,满足NEST={nif(ni)<fm,niOPEN}\mathrm{NEST} = \{n_{i}\,|\,f(n_{i}) < f_{m}, n_{i}\in \text{OPEN}\},并且在选择的时候优先选择NEST表中gg最小的节点,如果NEST空了再去OPEN表里面选择,并更新fmf_{m}

这种情况下仍可能会导致重复探索的出现!

其他搜索算法

例如爬山法,随机搜索算法,动态规划等,h(n)=0h(n) = 0的时候AA^{*}算法就变成了动态规划(?

Viterbi算法

Viterbi算法实例
Viterbi算法实例

转移方程为:

Q(Wij)={mink(Q(W(i1)k)+D(W(i1)k,Wij))i00i=0\begin{align*} Q(W_{ij}) = \begin{cases} \min\limits_{k}(Q(W_{(i-1)k}) + D(W_{(i-1)k, W_{ij}})) & i \neq 0 \\ 0 & i = 0 \end{cases} \end{align*}