当前位置: 移动技术网 > 移动技术>移动开发>IOS > 算法都是套路系列-搜索技术模板(4)

算法都是套路系列-搜索技术模板(4)

2020年10月09日  | 移动技术网移动技术  | 我要评论
基础算法模板总结与归纳系列——迭代加深、IDA*、搜索剪枝

🌸迭代加深搜索

迭代加深是一种 每次限制搜索深度的 深度优先搜索。

它的本质还是深度优先搜索,只不过在搜索的同时带上了一个深度d ,当 d达到设定的深度时就返回,一般用于找最优解。如果一次搜索没有找到合法的解,就让设定的深度加一,重新从根开始。

既然是为了找最优解,为什么不用 BFS 呢?我们知道 BFS 的基础是一个队列,队列的空间复杂度很大,当状态比较多或者单个状态比较大时,使用队列的 BFS 就显出了劣势。事实上,迭代加深就类似于用 DFS 方式实现的 BFS,它的空间复杂度相对较小。

当搜索树的分支比较多时,每增加一层的搜索复杂度会出现指数级爆炸式增长,这时前面重复进行的部分所带来的复杂度几乎可以忽略,这也就是为什么迭代加深是可以近似看成 BFS 的。

🍂步骤

首先设定一个较小的深度作为全局变量d,进行 DFS。每进入一次 DFS,将当前深度加一,当发现 d大于设定的深度limit 就返回。如果在搜索的途中发现了答案就可以回溯,同时在回溯的过程中可以记录路径。如果没有发现答案,就返回到函数入口,增加设定深度,继续搜索。

 IDDFS(u,d)
    if d>limit
        return
    else
        for each edge (u,v)
            IDDFS(v,d+1)
  return

在大多数的题目中,广度优先搜索还是比较方便的,而且容易判重。当发现广度优先搜索在空间上不够优秀,而且要找最优解的问题时,就应该考虑迭代加深。

🌸IDA*算法

伪代码

Procedure IDA_STAR(StartState)
Begin
  PathLimit := H(StartState) - 1;
  Succes := False;
  Repeat
    inc(PathLimit);
    StartState.g = 0;
    Push(OpenStack, StartState);
    Repeat
      CurrentState := Pop(OpenStack);
      If Solution(CurrentState) then
        Success = True
      Elseif PathLimit >= CurrentState.g + H(CurrentState) then
        For each Child(CurrentState) do
          Push(OpenStack, Child(CurrentState));
    until Success or empty(OpenStack);
  until Success or ResourceLimtsReached;
end;
  1. 空间开销小,每个深度下实际上是一个深度优先搜索,不过深度有限制,而 DFS 的空间消耗小是众所周知的;
  2. 利于深度剪枝。

缺点:重复搜索:回溯过程中每次 depth 变大都要再次从头搜索。

🍂 查看例题

🌸Dancing Links

详情

🌸搜索优化

DFS(深度优先搜索)是一种常见的算法,大部分的题目都可以用 DFS 解决,但是大部分情况下,这都是骗分算法,很少会有爆搜为正解的题目。因为 DFS 的时间复杂度特别高。(没学过 DFS 的请自行补上这一课)

既然不能成为正解,那就多骗一点分吧。那么这一篇文章将介绍一些实用的优化算法(俗称“剪枝”)。

先来一段深搜模板,之后的模板将在此基础上进行修改。

int ans = 最坏情况, now;  // now为当前答案
void dfs(传入数值) {
  if (到达目的地) ans = 从当前解与已有解中选最优;
  for (遍历所有可能性)
    if (可行) {
      进行操作;
      dfs(缩小规模);
      撤回操作;
    }
}

其中的 ans 可以是解的记录,那么从当前解与已有解中选最优就变成了输出解。

🍂剪枝方法

最常用的剪枝有三种,记忆化搜索、最优性剪枝、可行性剪枝。

💥记忆化搜索

因为在搜索中,相同的传入值往往会带来相同的解,那我们就可以用数组来记忆

int g[MAXN];  // 定义记忆化数组
int ans = 最坏情况, now;
void dfs f(传入数值) {
  if (g[规模] != 无效数值) return;  // 或记录解,视情况而定
  if (到达目的地) ans = 从当前解与已有解中选最优;  // 输出解,视情况而定
  for (遍历所有可能性)
    if (可行) {
      进行操作;
      dfs(缩小规模);
      撤回操作;
    }
}
int main() {
  ... memset(g, 无效数值, sizeof(g));  // 初始化记忆化数组
  ...
}

💥最优性剪枝

在搜索中导致运行慢的原因还有一种,就是在当前解已经比已有解差时仍然在搜索,那么我们只需要判断一下当前解是否已经差于已有解。

int ans = 最坏情况, now;
void dfs(传入数值) {
  if (now比ans的答案还要差) return;
  if (到达目的地) ans = 从当前解与已有解中选最优;
  for (遍历所有可能性)
    if (可行) {
      进行操作;
      dfs(缩小规模);
      撤回操作;
    }
}

💥可行性剪枝

在搜索中如果当前解已经不可用了还运行,也是在搜索中导致运行慢的原因。

int ans = 最坏情况, now;
void dfs(传入数值) {
  if (当前解已不可用) return;
  if (到达目的地) ans = 从当前解与已有解中选最优;
  for (遍历所有可能性)
    if (可行) {
      进行操作;
      dfs(缩小规模);
      撤回操作;
    }
}

🍂剪枝思路

剪枝思路有很多种,大多需要对于具体问题来分析,在此简要介绍几种常见的剪枝思路。

  • 极端法:考虑极端情况,如果最极端(最理想)的情况都无法满足,那么肯定实际情况搜出来的结果不会更优了。
  • 调整法:通过对子树的比较剪掉重复子树和明显不是最有“前途”的子树。
  • 数学方法:比如在图论中借助连通分量,数论中借助模方程的分析,借助不等式的放缩来估计下界等等。

例子可看第k个排列

本文地址:https://blog.csdn.net/weixin_45333934/article/details/108972924

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网