知识汇总-搜索

树与图的遍历,DFS,剪枝,迭代加深,BFS,双端队列BFS,优先队列BFS,A,K短路,IDA

树与图的遍历

  • DFS序:每个节点x的编号在序列中恰好出现两次。设这两次出现的位置为L[x]和R[x],那么闭区间[L[x],R[x]]就以x为根的子树的DFS序。
  • 树的重心,树的深度,图的连通块划分
  • 拓扑排序:拓扑排序可以判定有向图是否存在环。如果结果序列长度小于图中点的数量,则说明某些节点未遍历,进而说明有环。

剪枝

  • 优化搜索顺序。在一些搜索问题中,搜索树的各个层次、各个分支建的顺序不是固定的,不同的搜索顺序会产生不同的搜索树状态,其规模大小也相差甚远。如:小猫爬山时按照重量递减的顺序进行搜索,数独问题中优先搜索“能填的合法数字”最少的位置。
  • 排除等效冗余。当我们能判定从搜索树的当前节点上沿着几天不同分支可达的子树是等效的,那么只需要对其中一条分支执行搜索。
  • 可行性剪枝。有的分支永远无法到达边界,立即执行回溯。
  • 最优性剪枝。在最优化搜索问题中,如果当前的代价已经超过了当前搜到的最优解,那么就不用继续往下搜索了,执行回溯。
  • 记忆化。可以记录每个状态的搜索结果,在重复遍历一个状态时直接检索并返回。

迭代加深

  • DFS每次选定一个分支,不断深入,直到递归边界才回溯。当一颗搜索树的分支数目非常多,并且答案再某个较浅的节点上,如果在一开始就选错了分支,就很有可能在不包含答案的深子树上浪费很多时间。
  • 我们可以从小到大限制搜索的深度,如果在当前深度限制下搜不到答案就把深度增加,重新进行一次搜索。虽然在深度限制为d时,会重复搜索第1~d-1层节点,但是当搜索树节点分支数目较多是,随着层数的增加,每层节点数就会呈指数级增长,这些重复搜索与深层子树的规模相比就不值一提了。
  • 搜索树随着层次的深入增长很快,并且我们能够确保答案再一个较浅层次的节点时,就可以采用迭代加深的方法来解决问题。甚至有的题目描述包含“如果10步内搜不到就算无解”的字样。
  • 除了迭代加深外,双向搜索也可以避免在深层子树上浪费时间。有的题目不但有“初态”,还有明确的“终态”,并且从两个状态开始搜索产生的搜索树都能覆盖整个状态空间。这种情况下就可以双向搜索——从初态和终态出发各搜索一半,产生两个深度减半的搜索树,在中间交会、组合成最终的答案。

BFS(每一步扩展花费代价为1)

  • BFS逐层遍历搜索树,所有状态按照入队的先后顺序具有层次单调性。如果每一次扩展花费代价为1,那么当一个状态第一次被访问时,就得到了从起始状态到该状态的最小步数。
  • 值得一提的是,BFS队列中的元素关于层次满足两段性单调性,两段性就是在任意时刻,队列中至多有两个层次的节点;在访问完所有的i层节点后才会访问第i+1层节点。

双端队列BFS(每一步扩展花费代价只有0和1两种情况)

  • 如果每一步扩展花费代价要么是0,要么是1,我们就可以用双端队列在手动控制BFS的两段性和单调性。每个节点虽然可能被更新(入队)多次,但是它第一次被扩展出来时,就能得到对应的最优解,之后再被取出可直接忽略。

优先队列BFS(每一步扩展都有各自不同的代价)

  • 法1:仍然使用一般的队列,这时我们不能保证每个状态第一次入队就能得到最小代价,所有只能允许一个状态被多次更新、多次进出队列,不断搜索,直到队列为空,最后记录数组中保存了最小代价。复杂度一般会达到O(n^2),如最短路的SPFA算法。
  • 法2:改用优先队列进行BFS。每次从队列中取出当前代价最小的状态进行扩展(该状态一定是最优的,因为其他状态的代价都不小于它,所以之后就不可能再更新它了)。虽然这样每个状态也会被多次更新、多次进入队列,一个状态也可能以不同的代价在队列中同时出现,但是每个状态第一次出队就是最优解,后面再出队就可以直接忽略。所以每个状态只扩展一次,时间复杂度为O(nlgn),如对优化的Dijkstra。
  • 优先队列BFS的缺点就是不跑有负边的图。因为每次找出代价最小的状态时,因为负边的存在,不能保证此时它就是最优解,这时就只能用一般的队列搜索完整个解空间了。

双向BFS

  • 同之前的双向DFS,从初态和终态同时扩展,最小交会代价和就是答案。

A*(估价函数+优先队列+BFS)

  • 在优先队列BFS中,我们不断从堆中取出“当前最小代价”的状态进行扩展,如果给出一个目标状态 ,要求从初态到目标状态的最小代价,上述“优先策略”明显不完整。一个状态的当前代价最小,但在未来搜索中,可能当前状态到目标状态代价很大。
  • 我们设计一个“估价函数”,以任意状态为输入,计算出从该状态到目标状态所需要的估计值。在搜索中,仍然维护一个堆,每次从堆中取出“当前代价+未来估价”最小的状态进行扩展。
  • 设估价值为f(x),当前状态到目标状态的实际值为g(x),必须保证f(x) <= g(x)。具体证明P124。

K短路

  • 使用优先队列bfs时,第一次取出x点的状态就是从起点到x点的最优解,即最短路,同理,在第k次取出x时就是x的第k优解,即k短路。
  • 上诉方法虽然可行,但是最坏情况下时间复杂度为O(K*(N+N)*log(N+M)),使用A*提高搜索效率。
  • 在第K短路中从x到T的估计距离f(x)应该不大于第K短路中x到T的实际距离g(x),所以把估价函数f(x)定义为x到t的最短路。
  • 步骤:
    • dijkstra预处理出各个节点到t的最短路长度f(x)。
    • bfs时优先队列维护二元组(x,dist+f(x)),dist表示从s到x当前走过第距离,起初对重只有(s,0+f(s)).
    • 取出dist+f(x)最小的x,向外扩展。如果节点y被取出的次数未达到k,就直接把新的二元组(y,dist+w(x,y)+f(y))放入优先队列。
    • 重复2~3步,直到终点取出k次,返回答案。
  • 使用A*前后虽然时间复杂度上限相同,但是由于估价函数的作用,图中很多节点访问次数远小于K。

IDA*(迭代深搜+估价函数)

  • 若当前深度+未来估计步数>深度限制,立即返回。
  • 同样需要遵守f(x) <= g(x)的准则。
  • 只要f(x)够优秀,这个算法贼快。
Donate comment here