迭代加深 & 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;
}
相关推荐
lincats13 小时前
一步一步学习使用FireMonkey动画(6) 用实例理解动画的运行状态
ide·delphi·livebindings·delphi 12.3·firemonkey
啊我不会诶16 小时前
【图论】拓扑排序
算法·深度优先·图论
Ldawn_AI19 小时前
4+ 图论高级算法
算法·深度优先·图论
@Demi19 小时前
vsCode或Cursor 使用remote-ssh插件链接远程终端
服务器·ide·vscode·ssh
lincats20 小时前
一步一步学习使用FireMonkey动画(5) 动画图解11种动画插值类型
ide·移动开发·delphi 12.3·firedac·firemonkey
王伯爵20 小时前
Visual Studio Code (VS Code) 工作区配置文件的作用
ide·vscode·状态模式
南风里1 天前
Android Studio下载gradle文件很慢的捷径之路
android·ide·android studio
后天han1 天前
vscode中launch.json中定义的编译文件名于生成的不一致修改
ide·vscode·编辑器
lincats2 天前
一步一步学习使用FireMonkey动画(3) 使用Delphi的基本动画组件类
ide·delphi·delphi 12.3·firemonkey
在嵌入式里摸爬滚打2 天前
VScode远程连接Ubuntu报错问题分析
ide·vscode·编辑器