迭代加深 & IDE*

放在前面

1.DFS总结

2.在这一部分中,经常在dfs内部用到这样一种形式

复制代码
bool dfs(...)
{
    if(dfs(...))   return true;// 链式反应
    ...
    return false;
}

我们称上面在 d f s dfs dfs 内部直接返回 t r u e true true 的这种操作为链式反应 ,它会一路返回 t r u e true true,最终结束循环。

其含义就是只要有一种情况符合条件,那么最终答案就是 t r u e true true。

只有当所有情况都搜索完毕,还没有找到一个 t r u e true true 的条件,才返回 f a l s e false false

迭代加深搜索(IDDFS)

一、介绍

1.什么是迭代加深

首先,它是深度优先搜索 ,其次它与普通深度优先搜索不同的是,每次深搜都会有搜索的最大深度限制 ,如果没有找到解,那么就增大深度,再进行深搜,如此循环直到找到解为止,这样可以找到最浅层的解。

标志性的结构如下:

复制代码
 while(!dfs(maxdepth))
     maxdepth ++;

2.优势和劣势

了解了原理,大家也许会有疑问,那为啥不不直接用广度优先搜索呢?那是因为 I D D F S IDDFS IDDFS 有如下几个优势:

1.时间复杂度只比 B F S BFS BFS 稍差一点(虽然搜索 k + 1 k+1 k+1 层时会重复搜索 k k k 层,但是整体而言并不比广搜慢很多)。(在图解(2)中有说明)

2.空间复杂度与深搜相同,却比广搜小很多。

3.利于剪枝(迭代加深本质上还是 D F S DFS DFS ,而 D F S DFS DFS 利于剪枝, B F S BFS BFS 不便于剪枝)。

3.一个可能的疑问

我们已经知道,迭代加深是按照深度逐渐加深去搜索的,这就会导致产生大量重复搜索,那么如果重复搜索的太多,效率会不会比普通的 D F S DFS DFS 要低呢?

答案是不会的!

因为普通的 D F S DFS DFS 的增长规模是指数级别 的,我们重复搜索的 1 1 1 到 d − 1 d - 1 d−1 层的所有节点之和可能都没有第 d d d 层的节点多。

4.图解

(1)
(2)

5.使用时机

当搜索树非常深,但是我们能确定答案一定在浅层节点时,就可以使用迭代加深DFS。

二、例题

1.题目描述

模板题

2.分析

首先可以证得一个非常重要的结论(性质) ,本题序列的长度 m m m 肯定是不大于 10 10 10 的。

证明如下:

我们贪心的让每一个元素都是最大值,那么 X [ i ] = X [ i − 1 ] + X [ i − 1 ] X[i] = X[i - 1] + X[i - 1] X[i]=X[i−1]+X[i−1],此时的序列为: 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128... 1,2,4,8,16,32,64,128... 1,2,4,8,16,32,64,128...,我们发现,在 m = 8 m = 8 m=8 时,元素的大小就已经题目超过了题目给定的最大值 100 100 100。(然后详细的??不太会证了)

然后,通过这个性质,我们可以想到迭代加深这个做法,因为题目答案的深度是比较小的,而我们在搜索过程中可能会搜索很深的距离,最极端的情况,贪心的让每一个元素都是最小值,那么序列为 1 , 2 , 3 , 4 , 5 , 6..... 1,2,3,4,5,6..... 1,2,3,4,5,6.....,如果要搜索到最大值 100 100 100, d f s dfs dfs 的深度来到了 100 100 100 层,这是无法接受的。

3.代码

复制代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int n, path[17];
bool st[107];


bool dfs(int u, int max_depth)// 当前处于第几层,最大层数(从1开始)
{
    if(u == max_depth + 1)  return path[max_depth] == n;
    
    // 剪枝:避免重复搜索,每个数只搜索一次,st的值不需要回溯
    memset(st, false, sizeof st);

    // 从之前的序列中找出两个数
    // 剪枝:从序列中最大的数开始找的数大,结束快,可以减少搜索次数
    for(int i = u - 1; i >= 1; i -- )
    {
        for(int j = i; j >= 1; j -- )// 搜索[i,j]和[j,i]是等价的
        {
            int s = path[i] + path[j]; 
            if(s <= path[u - 1] || s > n)    continue;
            if(st[s])   continue;
            
            st[s] = true;
            path[u] = s;
            if(dfs(u + 1, max_depth))   return true;// 如果本次搜索成功,直接返回
        }
    }
    
    // 这里别忘了
    return false;
}

int main()
{
    path[1] = 1;
    while(cin >> n, n)
    {
        int depth = 1;
        if(n != 1)// 第一层恒的值恒为1,不需要搜索
        {
            depth ++ ;
            while(!dfs(2, depth))// 第一层恒为1,所以从第二层开始搜索
                depth ++ ;
        }
        
        for(int i = 1; i <= depth; i ++ )   cout << path[i] << ' ';
        cout << endl;
        
        // 我们在前面已经证明得到了depth<=10的结论,我们可以验证一下
        if(depth > 10)   puts("FALSE!");// 这句话在n<=100的条件下永远不会执行
    }
    
    
    return 0;
}

IDE*

一、介绍

IDA*的全称为:Iterative deepening A*,即基于迭代加深的 A*算法
IDA*实质上就是对迭代加深 加了一个启发式的剪枝

二、原理

在迭代加深代码框架基础上,对每个状态点引入一个估价值(该点距离目标点所需的最小步数),如果某状态点的深度加上该点的估价值>迭代加深限制的最大层数则直接返回不再向下层继续搜索

A*算法一样,都需要保证估价值 <= 真实值

该算法的难点仍在于如何确定估价函数

三、例题

1.题目描述

注:由于不同题目确定估价函数的方式各不相同,这里的题目仅是给出一个应用实例,无法做出推广
排书

2.分析

参考

本题的难点主要在于如何确定估价函数

由于我们的估价函数要小于真实值,所以我们希望我们的估价函数的含义可以表示为:最少需要多少步,可以把该序列变换到目标序列。

如该图下方所示,我们移动一段区间(假设该区间只包含一个元素 2 2 2 ),将它移动到一个数的后面,这里是将区间 [ 2 , 2 ] [2, 2] [2,2] 移动到 1 1 1 的后面,此时,有三个数的相邻关系被修改: ( 1 , 3 ) → ( 1 , 2 ) , ( 3 , 2 ) → ( 3 , 4 ) , ( 2 , 4 ) → ( 2 , 3 ) (1,3)\rightarrow(1,2),(3,2)\rightarrow(3,4),(2,4)\rightarrow(2,3) (1,3)→(1,2),(3,2)→(3,4),(2,4)→(2,3)

我们可以发现,移动一段区间,最多会改修三个数的相邻关系,也就是说,如果原序列中有 c n t cnt cnt 个数之间的相邻关系是错误的,那么我们最少需要 ⌈ c n t / 3 ⌉ \lceil cnt / 3 \rceil ⌈cnt/3⌉ 步就可以完成。

然后再 d f s dfs dfs 的过程中,每次选出一段区间,将这段区间放在某个数的后面,这个数必须在区间的右边(排除等效冗余)。

3.代码

复制代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 25;

int T, n;
int w[N], backup[5][N];
// backup 之所以开两维,第一位为5,是因为我们在u =1,2,3,4,5时都可能需要用到w数组
// 而题目规定深度最大为5,所以开到5

int f()// 含义:**至少**需要cnt步才能完成
{
    int cnt = 0;
    for(int i = 0; i + 1 < n; i ++ )
        if(w[i] + 1 != w[i + 1])
            cnt ++ ;
    return (cnt + 2) / 3; // <==> ceil(cnt / 3);
}

bool dfs(int u, int max_depth)
{
    if(u + f() > max_depth) return false; //A*
    if(f() == 0)    return true;

    //交换两个区间
    for(int len = 1; len <= n; len ++ )// 枚举区间大小
    {
        for(int l = 0; l + len - 1 < n; l ++ )
        {
            int r = l + len - 1;
            for(int k = r + 1; k < n; k ++ ) // 把当前区间放到k的后面
            {
                // 就相当于,把[r + 1, k]移到[l, r]的前面来,再把[l, r]移到后面
                memcpy(backup[u], w, sizeof w);
                int y = l;// y就指向当前位于那个位置
                for(int x = r + 1; x <= k; x ++ , y ++ )    w[y] = backup[u][x]; // 把[r + 1, k]移到[l, r]的前面来
                for(int x = l; x <= r; x ++, y ++ ) w[y] = backup[u][x];// 再把[l, r]移到后面
                if(dfs(u + 1, max_depth))   return true; //一路true返回
                memcpy(w, backup[u], sizeof w); // 回溯

            }
        }
    }

    return false; // 不要忘了
}

int main()
{
    cin >> T;
    while(T -- )
    {
        cin >> n;
        for(int i = 0; i < n; i ++ )   cin >> w[i];

        int depth = 0;
        while(depth < 5 && !dfs(0, depth)) depth ++ ;

        if(depth >= 5)  puts("5 or more");
        else cout << depth << endl;
    }
    return 0;
}

相关例题

例题一、IDE*

1.题目描述

回转游戏

2.分析

参考

3.代码

复制代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int pos[8][8] = {
    {0, 2, 6, 11, 15, 20, 22}, 
    {1, 3, 8, 12, 17, 21, 23}, 
    {10, 9, 8, 7, 6, 5, 4}, 
    {19, 18, 17, 16, 15, 14, 13}, 
    {23, 21, 17, 12, 8, 3, 1}, 
    {22, 20, 15, 11, 6, 2, 0}, 
    {13, 14, 15, 16, 17, 18, 19}, 
    {4, 5, 6, 7, 8, 9, 10}, 
};
int oppesite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int centre[8] = {6, 7, 8, 11, 12, 15, 16, 17};

int n, w[N], path[N];
int res, depth;

int f()
{
    static int num[4];
    memset(num, 0, sizeof num);

    for(int i = 0; i < 8; i ++ )    num[w[centre[i]]] ++ ;

    int maxn= -1;
    for(int i = 1; i <= 3; i ++ )   maxn = max(maxn, num[i]);

    return 8 - maxn;
}

void operate(int u)
{
    int t = w[pos[u][0]];
    for(int i = 0; i < 6; i ++ )    w[pos[u][i]] = w[pos[u][i + 1]];
    w[pos[u][6]] = t;
}

//当前深度,最大深度,上一个节点
bool dfs(int u, int max_depth, int last)
{
    if(u + f() > depth)   return false;
    if(f() == 0)   return true;

    for(int i = 0; i < 8; i ++ )
    {
        if(oppesite[i] != last)
        {
            operate(i);
            path[u] = i;
            if(dfs(u + 1, max_depth, i))    return true;
            operate(oppesite[i]);
        }
    }

    return false;//不返回false会出错
}

int main()
{
    while(cin >> w[0], w[0])
    {
        for(int i = 1; i < 24; i ++ )    cin >> w[i];

        //迭代加深
        depth = 0;
        while(!dfs(0, depth, -1))   depth ++ ;

        if(!depth) cout << "No moves needed" << endl;
        else    
        {
            for(int i = 0; i < depth; i ++ )   
            {
                char ch = path[i] + 'A';
                cout << ch;
            }
            cout << endl;
        }
        cout << w[6] << endl;
    }

    return 0;
}
相关推荐
滴答滴答嗒嗒滴13 分钟前
Python小练习系列 Vol.5:数独求解(经典回溯 + 剪枝)
python·深度优先·剪枝
Alger_Hamlet22 分钟前
Pycharm 2024.3 Python开发工具
ide·python·pycharm
Python之栈1 小时前
再见VS Code!Google IDE 正颠覆传统开发体验
ide·vscode·python
芥子沫1 小时前
远程装个Jupyter-AI协作笔记本,Jupyter容器镜像版本怎么选?安装部署教程
ide·人工智能·jupyter
komo莫莫da1 小时前
Day14 动态规划(3)
算法·深度优先·动态规划
txz20353 小时前
CMake在Windows环境下Visual Studio Code的使用
ide·vscode·编辑器
Arbori_262153 小时前
使用idea开发spark程序
java·ide·intellij-idea
代码很单纯,复杂的是人。5 小时前
platfromIO(VScode)串口print输出中文乱码
ide·vscode·编辑器
大哥喝阔落5 小时前
vscode_拼写关闭
ide·vscode·编辑器
qq_297504615 小时前
【解决】VsCode中code runner无法使用cin 输入
ide·vscode·编辑器