迭代加深和IDE*

放在前面

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

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

我们称上面在 \(dfs\) 内部直接返回 \(true\) 的这种操作为链式反应,它会一路返回 \(true\),最终结束循环。
其含义就是只要有一种情况符合条件,那么最终答案就是 \(true\)
只有当所有情况都搜索完毕,还没有找到一个 \(true\) 的条件,才返回 \(false\)

迭代加深搜索(IDDFS)

一、介绍

1.什么是迭代加深

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

 while(!dfs(maxdepth))
     maxdepth ++;

2.优势和劣势

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

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

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

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

3.一个可能的疑问

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

答案是不会的!

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

4.图解

(1)

IMAGE

(2)

IMAGE

5.使用时机

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

二、例题

1.题目描述

模板题

2.分析

首先可以证得一个非常重要的结论(性质),本题序列的长度 \(m\) 肯定是不大于 \(10\) 的。
证明如下:
我们贪心的让每一个元素都是最大值,那么 \(X[i] = X[i - 1] + X[i - 1]\),此时的序列为:\(1,2,4,8,16,32,64,128...\),我们发现,在\(m = 8\) 时,元素的大小就已经题目超过了题目给定的最大值 \(100\)。(然后详细的??不太会证了)

然后,通过这个性质,我们可以想到迭代加深这个做法,因为题目答案的深度是比较小的,而我们在搜索过程中可能会搜索很深的距离,最极端的情况,贪心的让每一个元素都是最小值,那么序列为 \(1,2,3,4,5,6.....\),如果要搜索到最大值 \(100\)\(dfs\) 的深度来到了 \(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.分析

参考

本题的难点主要在于如何确定估价函数
由于我们的估价函数要小于真实值,所以我们希望我们的估价函数的含义可以表示为:最少需要多少步,可以把该序列变换到目标序列。
IMAGE
如该图下方所示,我们移动一段区间(假设该区间只包含一个元素 \(2\) ),将它移动到一个数的后面,这里是将区间 \([2, 2]\) 移动到 \(1\) 的后面,此时,有三个数的相邻关系被修改:\((1,3)\rightarrow(1,2),(3,2)\rightarrow(3,4),(2,4)\rightarrow(2,3)\)
我们可以发现,移动一段区间,最多会改修三个数的相邻关系,也就是说,如果原序列中有 \(cnt\) 个数之间的相邻关系是错误的,那么我们最少需要 \(\lceil cnt / 3 \rceil\) 步就可以完成。

然后再 \(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.分析

参考

IMAGE

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;
}
posted @ 2022-06-02 10:28  光風霽月  阅读(27)  评论(0编辑  收藏  举报