[dfs] aw180. 排书(IDA*+dfs深入理解+思维+好题)

1. 题目来源

链接:180. 排书

相关链接:

2. 题目解析

IDA* 就是 基于迭代加深的 A * 算法

在 迭代加深 dfs 的基础上加上 A* 算法的剪枝就很就是 IDA*。即,在 dfs 过程中,针对每个节点都用估价函数估计它距离答案至少需要 dfs 多少层,如果当前层数加上估价层数大于了迭代加深设定的层数的话,很明显当前分支及以后的分支都是无法找到答案的,所以就可以直接剪枝了。

要求针对节点的估价距离一定要小于等于真实距离。

手写笔记,乱!
在这里插入图片描述


针对本题。

首先的首先,确定搜索顺序:枚举所有可选长度的连续区间,再枚举这些区间能放到的所有位置,就能枚举到所有方案。

估价函数设计:

  • 定义序列中每个数的后缀不匹配的情况。
  • 后缀匹配指的是当前序列位置上的数的后一个数刚好比当前位置的数大 1。
  • 终点序列是 123456… ,它们的后缀都是匹配的。
  • 针对当前节点状态,统计不匹配的后缀数量。一次的状态更新最好可以修复三个后缀数量,假设当前状态有 t 个后缀不匹配的位置,那么每次最多修复 3 个,至少需要修复 ⌈ t 3 ⌉ \lceil \frac t 3 \rceil 3t 次。即 dfs 至少需要再向下递归 ⌈ t 3 ⌉ \lceil \frac t 3 \rceil 3t 层才有可能搜到正确答案。
  • 可以以此配合迭代加深 depth 设置,来进行剪枝。

每个状态都是一组数,用数组存储。每次的状态更新、恢复现场都可以借鉴八数码问题的 backup 备份设计。

在此,同理可开一个 backup[N] 数组作为 局部变量 来存每个状态的当前层情况,dfs 向下递归时就直接在当前状态的数组中进行状态更新。状态需要回溯时,就用 backup 中备份的状态进行回溯即可。

但是显然这样做,每个节点都需要开一个局部变量数组来进行状态备份…空间复杂度就是 O ( n ) O(n) O(n) 的了。

我们需要理解

  • dfs 过程中栈内不会出现同层的节点。一条 dfs 路径一定是随着深度递增的。
  • 在回溯时,只需要当前路径中上面各层节点的状态,跟已经遍历过的节点、没遍历的节点、根本扯不上关系的节点都无关。
  • 所以,我们只需要开一个全局数组w[5][N] 用来存 dfs 每一层节点的状态,针对每条路径,只会存每层的一个节点的状态。
  • w[5][N] 数组中的状态就是反复覆盖使用,但能保证正确的回溯时的恢复现场。
  • 这样就将空间复杂度从 O ( n ) O(n) O(n) 优化到 O ( l o g n ) O(logn) O(logn) 了。 是一个非常棒的优化!
  • 但实际上,这两者在时间复杂度上是一样的。

具体看手写笔记吧,写的很详细:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


时间复杂度: O ( 56 0 4 ) O(560^4) O(5604),实际上使用 IDA* 后状态很少

空间复杂度: O ( 5 n ) O(5n) O(5n)


全局变量:

#include <iostream>
#include <cstdio>
#include <cstring> 
#include <algorithm>

using namespace std;

const int N = 15;

int n;
int p[N];           // 临时存当前层的状态
int w[5][N];        // 备份,记录当前状态,用于回溯时的状态恢复,直接拷贝恢复为之前原状态即可

// 统计后缀不匹配的个数,返回距离有序的最小操作步数,有序时返回 0
int f() {
    int res = 0;
    for (int i = 1; i < n; i ++ )
        if (p[i] != p[i - 1] + 1)
            res ++ ;
    
    return (res + 2) / 3;                   // 上取整,得到当前状态距离最终状态的最小距离
}

bool dfs(int u, int depth) {
    if (u + f() > depth) return false;      // 启发式剪枝
    if (!f()) return true;                  // 当估价函数返回 0,等价于排好序了

    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 的后面
                memcpy(w[u], p, sizeof p);              // 备份当前状态
                
                // 状态更新,需要将 len 这段放到 k 的后面
                // [r+1, k] 需要放到 [l, r] 的前面,类比归并排序
                int y = l;
                for (int x = r + 1; x <= k; x ++ , y ++ ) p[y] = w[u][x];
                for (int x = l; x <= r; x ++ , y ++ ) p[y] = w[u][x];

                if (dfs(u + 1, depth)) return true;
                
                /*
                 * 打印当前层的正确状态,可以看到是怎么进行 5 步内更改的 
                 * 递归,是按状态倒序打印的,且最终有序时,会在 if (!f()) return true; 直接 return ; 
                 * 不会打印有序的状态
                if (dfs(u + 1, depth)) {
                    for (int i = 0; i < n; i ++ ) cout << w[u][i] << ' ';
                        cout << endl;
                    return true;
                }
                */

                memcpy(p, w[u], sizeof p);          // 恢复现场
            }
        }

    return false;
}

int main() {
    int T;
    scanf("%d", &T);

    while (T -- ) {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%d", &p[i]);   // 初始状态

        int depth = 0;
        while (depth < 5 && !dfs(0, depth)) depth ++ ;      // 层数小于 5 且没搜到答案,则迭代加深继续搜

        if (depth >= 5) puts("5 or more");
        else printf("%d\n", depth);         
    }

    return 0; 
}

局部变量:

#include <iostream>
#include <cstdio>
#include <cstring> 
#include <algorithm>

using namespace std;

const int N = 15;

int n;
int p[N];           // 临时存当前层的状态

int f() {
    int res = 0;
    for (int i = 1; i < n; i ++ )
        if (p[i] != p[i - 1] + 1)
            res ++ ;
    
    return (res + 2) / 3;                 
}

bool dfs(int u, int depth) {
    if (u + f() > depth) return false;    
    if (!f()) return true;                 

    int w[N];		// 针对每个节点,都开一个空间,用来存储原状态,用于回溯
    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 ++ ) {        
                memcpy(w, p, sizeof p);             
                int y = l;
                for (int x = r + 1; x <= k; x ++ , y ++ ) p[y] = w[x];
                for (int x = l; x <= r; x ++ , y ++ ) p[y] = w[x];
                if (dfs(u + 1, depth)) return true;
                memcpy(p, w, sizeof p);      
            }
        }

    return false;
}

int main() {
    int T;
    scanf("%d", &T);

    while (T -- ) {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%d", &p[i]);   

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

        if (depth >= 5) puts("5 or more");
        else printf("%d\n", depth);         
    }

    return 0; 
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的影城管理系统,源码+数据库+论文答辩+毕业论文+视频演示 随着现在网络的快速发展,网上管理系统也逐渐快速发展起来,网上管理模式很快融入到了许多生活之中,随之就产生了“小徐影城管理系统”,这样就让小徐影城管理系统更加方便简单。 对于本小徐影城管理系统的设计来说,系统开发主要是采用java语言技术,在整个系统的设计中应用MySQL数据库来完成数据存储,具体根据小徐影城管理系统的现状来进行开发的,具体根据现实的需求来实现小徐影城管理系统网络化的管理,各类信息有序地进行存储,进入小徐影城管理系统页面之后,方可开始操作主控界面,主要功能包括管理员:首页、个人中心、用户管理、电影类型管理、放映厅管理、电影信息管理、购票统计管理、系统管理、订单管理,用户前台;首页、电影信息、电影资讯、个人中心、后台管理、在线客服等功能。 本论文主要讲述了小徐影城管理系统开发背景,该系统它主要是对需求分析和功能需求做了介绍,并且对系统做了详细的测试和总结。具体从业务流程、数据库设计和系统结构等多方面的问题。望能利用先进的计算机技术和网络技术来改变目前的小徐影城管理系统状况,提高管理效率。 关键词:小徐影城管理系统;Spring Boot框架,MySQL数据库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值