(算法提高课)搜索-迭代加深

迭代加深

1. 什么是迭代加深
2. 例题170. 加成序列

关于迭代加深

在这里插入图片描述

在学习迭代加深深搜之前,我们先简单回顾一下深搜。深搜的本质是对图的深度优先遍历。也就是“先往深了走,走完了没找到就换条路继续走”。对于那种无法一眼看出来是图论的问题,我们需要先把问题给定的条件抽象为一棵“搜索树”,然后在抽象出来的搜索树上进行深搜,解决问题。

假设上图就是我们针对某一个问题所抽象出来的搜索树。我们给定答案节点是4号节点。

那么,根据深搜的定义,我们进行算法的顺序是1-2-5-10-11-6-12-13-3-7-14-15-16-17-8-4-9.

此时出现了某些分支的深度特别深,但是答案的深度很浅,搜索效率低下的情况;因此使用迭代加深的应用情形就是:当搜索树非常深,但当能确定答案一定在浅层节点时,就可以使用迭代加深DFS

如运用迭代加深方法再次搜索4号节点的过程就变为:
假如限制是1,不行。
限制是2,搜到,结束。
假设答案是9号点的话,就再限制3,搜到,结束。
但是要提及的是,我们的搜索方式并不是扩展,而是迭代。
也就是说,限制是1,我们搜了1号点,没搜到答案。
限制换成2的时候,我们就需要重新从1号点开搜,而不是在原有的基础上继续拓展

所以算法过程就是给搜索设置一个约束,当搜索深度达到约束值却仍然没有找到可行解的时就结束搜索;增大搜索深度,再次搜索,直至搜索到最优解

在实际运用中,如果没有一个合适的方法来剪枝,迭代加深搜索也会很容易超时。好在迭代加深搜索有一个比较特殊的剪枝方法,就是对当前的情况通过一个乐观估计函数进行预估,如果发现即使在最好的情况下搜索到当前的最深深度限制也没办法得到答案,那么就及时退出来实现剪枝。

当然,注意一下,使用迭代加深搜索的时候,一定要确定这个问题是有解的,否则会陷入不断加深的死循环。

170. 加成序列

满足如下条件的序列 X(序列中元素被标号为 1、2、3…m)被称为“加成序列”:
X[1]=1
X[m]=n
X[1]<X[2]<…<X[m−1]<X[m]
对于每个 k(2≤k≤m)都存在两个整数 i 和 j ( 1 ≤ i , j ≤ k − 1 1≤i,j≤k−1 1i,jk1,i和 j 可相等),使得 X [ k ] = X [ i ] + X [ j ] X[k]=X[i]+X[j] X[k]=X[i]+X[j]
你的任务是:给定一个整数 n,找出符合上述条件的长度 m最小的“加成序列”。
如果有多个满足要求的答案,只需要找出任意一个可行解。
输入格式
输入包含多组测试用例。
每组测试用例占据一行,包含一个整数 n。
当输入为单行的 0时,表示输入结束。
输出格式
对于每个测试用例,输出一个满足需求的整数序列,数字之间用空格隔开。
每个输出占一行。
数据范围
1≤n≤100
输入样例:
5
7
12
15
77
0
输出样例:
1 2 4 5
1 2 4 6 7
1 2 4 8 12
1 2 4 5 10 15
1 2 4 8 9 17 34 68 77

找了半天分析IDDFS和BFS使用区别的贴子,简单搜索了一下没有看到比较合适和清晰的,就结合题目来看吧

很明显在本题中,每一个方案的生成,因为需要满足和式需求,最后一个数字的生成都依赖于前面的数字,也即需要往根节点溯源。这显然更符合DFS的搜索逻辑,因为此时搜索更加关注根节点而非同层节点,而且搜索顺序通常是从1开始直至搜索到一个可能的解,也即搜索到 n 为止;也即本题应该是需要运用DFS的搜索逻辑

但本题除了求解之外,还需要求长度最短的解,也即最优解问题。这个最优类似于求步数或者是说层数,也即从根节点出发,若按层次搜索,第一次搜索到解的时候一定层数最短,也即解最优。此时和BFS的逻辑有相同之处;所以整体来说IDDFS是一个以DFS搜索逻辑为主体(更改搜索约束后的每一次搜索都是从根节点重新开始的一次DFS),结合了DFS和BFS优点的一个算法

所以可以归纳出本题的搜索顺序:依次考虑每一个位置的数字是什么;枚举出所有的数字可能

除了基本搜索顺序逻辑之外,本题还需要对搜索树考虑合适的剪枝

剪枝1:保证分支数量较少,优先枚举较大的数字,此时可以保证数据的增长速度,使得最靠近 n 的数字优先被搜索,使得提早完成搜索的可能性变大
剪枝2:排除等效冗余。搜索过程中需要通过枚举前两个数的和来枚举当前位置应该放置的数字,而两个数的和可能会出现相同的情况,若不进行剪枝,可能会建立相同根节点的搜索树,造成冗余搜索。解决方法是可以开一个BOOL数组,来判断当前数字是否有被枚举过

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int path[N];
int n;

bool dfs(int now, int depth)  //从根节点的下一层,也即第一层开始搜索
{
    if(now == depth) return path[now - 1] == n;

    bool vis[N] = {0};
    //此时的bool 数组没有开全局;要理解这个bool数组的含义,只是在填入now层也即当前层的数字时,判断枚举是否有重复,开全局数组还会有维护难度,根据题意这里直接开成函数内部的BOOL数组就可以了
    for(int i = now - 1;i >= 0;i --){
        for(int j = i;j >= 0;j --){
            int s = path[i] + path[j];
            if(vis[s] || s <= path[now - 1] || s > n) continue;
            path[now] = s;
            vis[s] = true;
            //这里是不需要回撤的,因为公用一个数组,只要是搜索到now层,都会填写如path[now]中,直接覆盖了
            if(dfs(now + 1, depth))  return true;
        }
    }

    return false;
}
int main()
{
    path[0] = 1;  //序列的第一个数字只能为1
    while(cin >> n, n){
        int depth = 1;  //搜索深度限制

        //在[1,depth)的层数限制中搜索解
        while(!dfs(1, depth)) depth ++;   //注意是while不是if,只要没有搜索到解,一直搜索下去

        for(int i = 0;i < depth;i ++) cout << path[i] << " ";
        cout << endl;
    }
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值