6.6 搜索讲解
By i207M
DFS(图)
深度优先遍历图的方法是,从图中某顶点v出发:
-
访问顶点v;
-
依次从v的未被访问的邻接点出发,对图进行深度优先遍历,直至图中和v有路径相 通的顶点都被访问;
-
若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先 遍历,直到图中所有顶点均被访问过为止。
按照深度优先的策略扩展出所有可能状态,在遍历完所有后继状态后回溯。 搜索的过程是一棵DFS树。
广泛应用在题目的暴力做法中。至于你的暴力能拿到多少分就取决于你的剪枝技巧了。
DFS剪枝
可行性剪枝:如果当前状态不管怎么搜到最后一定是违法的状态,回溯。
最优性剪枝:如果当前状态搜到最后不可能比当前最优解要优,回溯。经常与估价函数配合使用。
一道题可能有多个剪枝
搜索顺序也会优化时间:先搜扩张状态少的快,有时倒搜比正搜快,有时瞎搜比正常搜快
虫食算:可行性剪枝。把当前的已知字母带到竖式中算一遍,如果出现矛盾情况 就剪枝。每次搜索时从后向前判断每个式子合法性;从低到高搜索字母;某一位确定了两个数,第三个数自然就确定了
生日蛋糕:最优性剪枝。从下往上搜;当前的奶油面积+之后的最小奶油面积>现在已求出的的最小奶油面积——果断return,当前的体积+之后的最大体积<体积总数,果断return,易得
BFS
事实上就是在状态图中求边权都为1的最短路,相当于在DFS树上每次拓展一层。
对于BFS来说,剪枝的重要性弱化,或者说,BFS的剪枝是对状态去重。
软件补丁问题:\(2^n\) 个状态建图,最多 \(20\) 个错误,用 \(32\) 位整数的每一位保存一种错误。 把补丁当做边,边数太多不用存,用BFS跑最短路。
先要找出所有状态且不得有重复状态。一个状态拿三元组表示 \((i,j,0/1/2)\),表示第一块在 \((i,j)\) 这个格子, \(k=0\) 表示立着, \(k=1\) 表示 \((i+1,j)\) 还有一块, \(k=2\) 表示 \((i,j+1)\) 还有一块。 枚举下一步怎么走转移。
双向BFS
就是从终点和起点同时开始bfs,直到他们相遇。 其实没必要同时搜,可以和折半爆搜一样先搜一边。
骑士精神:题目都写的明明白白,最多15步,这启发我们直接对步数进行限制。直接双向BFS即可
折半爆搜
这个算法思想的核心是把一个问题分成两半然后合并,这样因为少了一半的搜索层数, 搜索速度就会快很多。然后再使用一些科技把两边拼起来。
方程的解数:可以哈希但这里不用。考虑到直接搜索时间复杂度爆炸,我们将方程对个折,先搜索出前一半未知数的所有情况,即前一半多项式算出的结果可能是多少,然后再将前后合并,如果加和为 \(0\) 那么就是 一种合法情况。合并的时候由于情况比较多,无法 \(O(n^2)\) 描。比较是否为相反数,需要排序后用双指针扫
\(A^*\)
可理解为进阶版的最优性剪枝
是一种静态网中求解最短路最有效的直接搜索方法
主要用来求最优解或第k优的解
k短路问题:\(A^*\)算法的经典应用,另一种做法是可持久化左偏树。
第一个想法:这题是搜索题。
第二个想法:如果是搜索,怎么搜?宽搜还是广搜,搜什么?
第三个想法:我要保证第k短,是先搜出来很多路径再排序还是搜的时候就按照权值从大 到小搜?
第四个想法:我搜出来的路径怎么确定不会有重复呢?
第五个想法:我能不能用上之前所学的知识优化我的搜索呢?
我们如果BFS/DFS所有路径的话,最终的复杂度是指数级的。
我们之前扩张出大量状态的原因是前面的状态的权值完全没有考虑后面应该如何走,前 后状态没有可比性。如果我们能给每个状态一个到终点的估价的话,也就是所有状态都 有一个统一的预报代价,使得前后的点具有可比性,那么我们的搜索就不会那么盲目。
形式化地,我们设当前点的 \(i\) 已经走过的路程为 \(f(i)\) ,我们再想办法估计一下这个状态到终 点最少还要走 \(g(i)\) 的距离,这样的话我们用函数 \(h(i)=f(i)+g(i)\) 就能比较两个状态的相对大 小。
使用堆,每次选择 \(h(i)\) 小的向前扩展。
\(IDA^*\)
ID:迭代加深
埃及分数:如果单单限制层数的话,状态数量过于多,广搜需要很大的空间来存储 状态,所以这道题并不适合广搜。深搜的话我们可以用一些剪枝,但是我们还是没有办法判断什么时候停止搜索,因为小 的数可能无限叠加。我们如果给深搜规定一个层数限制的话,如果当前枚举到的分数乘以剩下层数还比剩下 的分数要小的话,那么这个状态就可以跳出来了!这样我们的搜索就不会无限进行下去。 我们从1开始枚层数,一个一个搜,使用简单的A*进行估价,第一次搜到的可行解就是答案。
每次新设定一个层数再搜一遍不会把之前的状态再搜一遍吗? 确实是这样的,但是多一层状态会多出来很多很多,所以之前的再搜一遍也不会增加多少时间复杂度。 举例:二叉搜索树。
记忆化搜索
严格的来说,这部分知识属于DP。记忆化搜索只是DP的一种写法,而且往往非常好写, 尤其是转移的式子。
这种写法也可以用于,你设计了一个DP,但是DP的复杂度过高,但是你很有信心,你相 信实际上有用的状态不多,于是你就信仰记忆化搜索。可以用map/unordered_map
存储状态。
只要数据水且空间够,DP常常能骗到高分
滑雪:DP是可以做的,我们需要将矩阵变成一个拓扑图,太麻烦。
我们设 \(f[i]\) 为到 \(i\) 为止最长路径的长度,扫描这个点周围的点,易得
当一个点四周的点都比它矮的时候 \(f[i]=1\) .
每次搜索完这个点之后将答案记录下来,下次不再进行搜索,直接返回答案。每个点的答案都只会被更新一次,每个点需要四次扫描周围的点,时间复杂度 \(O(RC)\) 。
模拟退火
总的精神就是:随机,遇到更好的结果就接受,随机的过程逐渐趋向于收敛。但是为了防止陷入局部最优解,要有一些概率接受不太好的结果,这个过程是向金属退火的过程学习。
平衡点:
void sa(){
double x=ansx,y=ansy,T=2e4;
while(t>1e-14){
d tx=x+(rand()<<1)-RAND_MAX)*T,ty=x+(rand()<<1)-RAND_MAX)*T;
double tans=calc(tx,ty),d=tans-ansv;//通常这种题改一下这行和上一行就行了
if(d<0.0){
ansv=tans;
ansx=tx,ansy=ty;
x=tx,y=ty;
}
else
if(exp(-d/T)*RAND_MAX>rand())
x=tx,y=ty;
T*=delt;
}
}
后面没讲完,咕咕咕……