【学习笔记】搜索

· · 算法·理论

1. 爆搜

显然,就是暴力算法。(当然也是所有 OIer 初学时就会了解到的东西)

2. 剪枝

(1)最优性剪枝

定义很简单,也很常用。就是指的如果当前答案比我们当前的最优解更劣,且不会变得更优,那么就是不行的。

(2)可行性剪枝(上下界剪枝)

如题,如果说,当前状态不合题意,那么此时这个答案就“废”了,我们就不必搜索。

(3)记忆化

如果我们要搜索答案,并且这个答案唯一,那么我们可以把这个答案记录下来,然后以后每次访问直接得到这个答案即可。

(4)排除冗余状态

如果我们不需要求解,只需要遍历,那么这个答案就没必要。我们就可以打个标记,如果遍历过,那么就不再遍历。

(5)优化搜索顺序

如果说,对于某个东西,我们需要使它更优,那么,我们可以按照从优到劣的顺序来搜索,这样第一次搜到的答案就是最优的,就不需要继续搜索。

(6)随机化贪心

如果搜索状态过多,并且你想不到除了爆搜之外的其他解法,但是暴力过不了,那么我们可以确定评测机可以搜索的次数,如果超过了这个次数,那就不搜了,这样就可以用正确性保证时间。

如果某个点遍历得太多,那么我们也可以确定每个点的遍历次数来求出答案,时间复杂度是线性。

3.双向搜索

众所周知,搜索的复杂度是指数级的,当然指的是理论复杂度,实际会跑不满稍微快一点。

所以,在已知初始状态和最终状态的情况下,我们可以同时从这两个状态开始搜索,那么搜索树的规模应该会减半,就可以把时间复杂度的指数优化到原来的一半。

4.广搜变形

首先,我们要了解一下广搜的队列实现原理:保证队列具有单调性,这样第一次查找到的答案就是最优解(其实就是运用了最优性剪枝)。

但是,如果边权不唯一,插入到队尾的数就没有了单调性,我们分两种情况讨论:

(1)0/1 边权

如果此时队列中的队首答案如果是 s在保证单调性的情况下,队首就是 s(你取出来的),队尾的答案就是 s+1(显然,因为 s 是正在扩展的,而且只能扩展到 s+1)。但是你的权值扩展出来也只能是 s(边权为 0)和 s+1(边权为 1)。也就是说,如果扩展的边边权为 0 放入队首,边权为 1 放入队尾,可以使用双端队列维护。

(2)所有边权

那么我们不只有两个边权了,为了保证单调性,我们可以实现一个满足单调性的数据结构——优先队列(priority_queue)。注意,这里会使时间复杂度多了一个 O(\log n)

还需提醒的是,这里不能使用单调队列,因为它会删除一些状态,可能会把一些有解的情况判定成无解。

5.迭代加深

有一类特殊的搜索:求答案的最小值。

这里存在一个最优性剪枝,但是不能优化整体的复杂度,此时可以考虑迭代加深。

首先,我们要理解这个词的意思:深,就是指搜索深度,也就是答案;迭代加,就是在使搜索深度越来越大。

也就是说,我们可以控制一个深度,每次搜索,如果有解,那么这个深度就是最小深度,也就是最优解。

注意常数问题,所以说剪枝不可减免。

6.A*

A* 是一个非常奇妙的算法,它的主旨在于 F(x)=g(x)+h(x)

$h(x)$ 是指预估要走的步数,注意要保证 $h(x)\leq\texttt{已经走过的步数}$,这样才能保证正确性。 $F(x)$ 是 $g(x)+h(x)$,是我们的估价函数,也就是说我们至少的答案值。 那么,我们先遍历答案小的,再遍历答案大的,这样我们就可以保证答案的最优性。这个过程可以用优先队列(`priority_queue`)维护。 ## 7.IDA\* 我们可以发现 A\* 的维护多了一个 $O(\log n)$,那么我们可以类比迭代加深,可以把 A\* 融入迭代加深,于是就变成了 IDA\*。 注意,IDA\* 的优化在深度判断上:如果 $F(x)>\texttt{限制深度}$,那么无解,退出。