IDA*——AcWing 180. 排书

IDA*

定义

IDA*(Iterative Deepening A*)是一种结合了深度优先搜索(DFS)的递归深度限制特性和A搜索的启发式估价函数的搜索算法。它主要用于解决启发式搜索问题,尤其是当搜索空间很大或者搜索成本不确定时。

IDA* 是一种最佳优先搜索算法,其基本思想是在深度优先搜索的基础上,通过逐步增加搜索深度并结合A*算法中的估价函数f(n)=g(n)+h(n),来找到从初始节点到目标节点的最短路径。其中,g(n)是从初始节点到当前节点的实际代价,h(n)是从当前节点到目标节点的启发式估计代价。

运用情况

  1. 内存限制: IDA特别适合那些内存有限制的环境,因为它不像A那样需要维护一个开放列表(open list),而是采用递归的方式逐步深入搜索。
  2. 未知或变化的成本: 当搜索空间中节点的移动成本不是预先完全已知或可能变化时,IDA*通过逐步探索来适应这种不确定性。
  3. 最优解搜索: 当寻找问题的最优解而非任一解时,IDA*通过其最佳优先的特性保证找到的是从起点到终点的最短路径。
  4. 大型或无限状态空间: 对于状态空间非常大或理论上无限的情况,IDA*通过逐步加深搜索深度来逐步逼近解,避免了一次性需要大量内存来存储整个搜索树的问题。

注意事项

  1. 启发式函数的选择: h(n)的选择至关重要,它需要保证不大于实际的最小代价,否则算法可能不会终止。理想的h(n)接近实际成本但又易于计算。
  2. 深度限制: 初始时设置一个合理的深度限制,随着搜索的进行逐渐增加,直到找到解或达到某个预设的深度上限。
  3. 效率: 虽然IDA避免了A中的开放列表维护,但如果启发式不够好或搜索空间极大,递归调用可能会导致大量的重复计算和较慢的收敛速度。
  4. 栈溢出风险: 由于使用递归,如果搜索深度过大,可能会导致栈溢出。在实现时可以考虑使用迭代版本来避免这一问题。

解题思路

  1. 初始化: 设定初始深度限制d=0,以及一个足够大的上限D作为退出条件。
  2. 计算阈值: 使用当前深度限制计算f(n)的阈值,即f_limit = g(start) + d * h(start),其中g(start)=0,因为是从初始节点开始。
  3. 深度优先搜索: 从初始节点开始执行深度优先搜索,只有当节点的f(n)=g(n)+h(n)小于当前的f_limit时才继续扩展该节点的子节点。
  4. 递归加深: 如果在当前深度限制下没有找到解,则增加深度限制d=d+1,回到步骤2,继续搜索。
  5. 结束条件: 当搜索到目标节点或达到预设的深度上限D时,结束搜索。

AcWing 180. 排书   

题目描述

180. 排书 - AcWing题库

运行代码

#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 15;  // 书的数量上限
using namespace std;
int n;  // 实际的书的数量
int q[N];  // 存储书的初始排列
int w[5][N]; // 辅助数组,用于在搜索过程中保存中间状态 
// 估价函数:计算当前状态与目标状态的差异,返回还需要多少步才能达到目标状态
int f() { 
    int tot = 0;
    for (int i = 0; i + 1 < n; ++i) {
        if (q[i + 1]!= q[i] + 1) {
            ++tot;
        }
    }
    return (tot + 2) / 3;
} 
// 深度优先搜索函数
bool dfs(int depth, int max_depth) { 
    if (depth + f() > max_depth) { // 如果当前深度加上估计的剩余深度超过了限制深度,进行剪枝
        return false; 
    }
    if (f() == 0) { // 如果估价函数为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) { // 枚举要插入的位置(连续段的结束位置的后面)
                memcpy(w[depth], q, sizeof q); // 复制当前状态到辅助数组
                int y = l; 
                for (int x = r + 1; x <= k; x++, y++) { // 将连续段插入到指定位置
                    q[y] = w[depth][x]; 
                }
                for (int x = l; x <= r; x++, y++) {
                    q[y] = w[depth][x]; 
                }
                if (dfs(depth + 1, max_depth)) { // 递归搜索下一层
                    return true; 
                }
                memcpy(q, w[depth], sizeof q); // 如果不成功,恢复当前状态 
            }
        }
    }
    return false; 
} 
int main() { 
    int T; 
    cin >> T; 
    while (T--) { 
        cin >> n; 
        for (int i = 0; i < n; ++i) {
            cin >> q[i]; 
        }
        int depth = 0; 
        // 迭代加深搜索,从深度0开始,每次增加1,直到找到解或者深度达到5
        while (depth < 5 &&!dfs(0, depth)) { 
            depth++; 
        }
        if (depth >= 5) {
            cout << "5 or more" << endl; 
        } else {
            cout << depth << endl; 
        }
    }
    return 0; 
}

代码思路

  • 估价函数 f():用于估计从当前状态到达目标状态(书按照 1∼n 的顺序依次排列)还需要的最少操作次数。通过计算当前排列中顺序不正确的后继关系的数量 tot ,然后返回 (tot + 2) / 3 作为估计值。这是因为每次移动一个连续段最多可以改变 3 个元素的后继关系。
  • dfs(int depth, int max_depth) 函数:进行深度优先搜索。
    • 参数 depth 表示当前搜索的深度(即已经进行的操作次数)。
    • max_depth 是限制的最大深度。如果在当前深度 depth 下,加上估计的还需要的最少操作次数 f() 超过了 max_depth ,就意味着即使后面按照最优方式操作也无法在限制深度内达到目标状态,此时进行剪枝(直接返回 false ,不再继续搜索该路径)。
    • 通过三重循环来枚举移动连续段的长度、起始位置和插入位置,模拟进行抽取连续段并插入到其他位置的操作。
    • 在每次尝试一种可能的操作后,递归调用 dfs(depth + 1, max_depth) 搜索下一层。如果找到解(即达到目标状态),则返回 true ,否则恢复原来的状态(通过 memcpy(q, w[depth], sizeof q); ),继续尝试其他可能的操作。
  • 主函数中的迭代加深搜索:从深度 0 开始,每次增加 1,调用 dfs(0, depth) 进行搜索。如果在深度小于等于 4 时找到了解,就输出对应的操作次数;如果直到深度达到 5 还没有找到解,就输出 "5 or more" 。

改进思路

  1. 优化内存使用:可以减少使用的辅助数组数量,或者使用更高效的数据结构来存储中间状态。
  2. 改进剪枝策略:可以进一步优化估价函数,使其更加准确,从而增强剪枝效果,减少不必要的搜索。
  3. 优化循环结构:对于一些循环的边界条件和步长进行优化,以减少不必要的计算。

改进代码

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

const int N = 15;  // 书的数量上限

int n;  // 实际的书的数量
int q[N];  // 存储书的初始排列

// 估价函数:计算当前状态与目标状态的差异,返回还需要多少步才能达到目标状态
int f() { 
    int tot = 0;
    for (int i = 0; i + 1 < n; ++i) {
        if (q[i + 1]!= q[i] + 1) {
            ++tot;
        }
    }
    return (tot + 2) / 3;
} 

// 深度优先搜索函数
bool dfs(int depth, int max_depth) { 
    if (depth + f() > max_depth) { // 如果当前深度加上估计的剩余深度超过了限制深度,进行剪枝
        return false; 
    }
    if (f() == 0) { // 如果估价函数为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) { // 枚举要插入的位置(连续段的结束位置的后面)
                int temp[N];
                memcpy(temp, q, sizeof(q));  // 复制当前状态

                int y = l; 
                for (int x = r + 1; x <= k; x++, y++) { // 将连续段插入到指定位置
                    q[y] = temp[x]; 
                }
                for (int x = l; x <= r; x++, y++) {
                    q[y] = temp[x]; 
                }

                if (dfs(depth + 1, max_depth)) { // 递归搜索下一层
                    return true; 
                }
                memcpy(q, temp, sizeof(q)); // 如果不成功,恢复当前状态 
            }
        }
    }
    return false; 
} 

int main() { 
    int T; 
    std::cin >> T; 
    while (T--) { 
        std::cin >> n; 
        for (int i = 0; i < n; ++i) {
            std::cin >> q[i]; 
        }
        int depth = 0; 
        // 迭代加深搜索,从深度0开始,每次增加1,直到找到解或者深度达到5
        while (depth < 5 &&!dfs(0, depth)) { 
            depth++; 
        }
        if (depth >= 5) {
            std::cout << "5 or more" << std::endl; 
        } else {
            std::cout << depth << std::endl; 
        }
    }
    return 0; 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/782001.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SprongBoot及其基础应用全套部署脚本和配置

POM.xml配置 </dependencies> <!--skywalking日志监控依赖--><dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-toolkit-logback-1.x</artifactId><version>8.5.0</version></dependency&g…

轻松驾驭开发之旅:Maven配置阿里云CodeUp远程私有仓库全攻略

文章目录 引言一、为什么选择阿里云CodeUp作为远程私有仓库&#xff1f;二、Maven配置阿里云CodeUp远程私有仓库的步骤准备工作配置Maven的settings.xml文件配置项目的pom.xml文件验证配置是否成功 三、使用阿里云CodeUp远程私有仓库的注意事项 引言 在软件开发的世界里&#…

软件工程(上)

目录 软件过程模型&#xff08;软件开发模型&#xff09; 瀑布模型 原型模型 V模型 构件组装模型 螺旋模型&#xff08;原型瀑布&#xff09; 基于构件的软件工程&#xff08;CBSE&#xff09; 快速应用开发模型&#xff08;RAD&#xff09; 统一过程&#xff08;UP&a…

Http Json参数到x-www-form-urlencoded参数的在线转换工具

Json参数到x-www-form-urlencoded参数的在线转换工具

C语言 printf 函数多种输出格式以及占位输出

一、输出格式 在C语言中&#xff0c;printf 函数提供了多种输出格式&#xff0c;用于控制不同类型数据的输出方式。 1.整数输出格式 %d&#xff1a;以十进制形式输出整数。 %o&#xff1a;以八进制形式输出整数&#xff08;无前导0&#xff09;。 %x 或 %X&#xff1a;以十六进…

CMD命令详细介绍 | 超详细版本!

文章目录 启动cmd命令用户启动使用管理员的账号启动 文件夹命令网络命令其他常用命令介绍常用快捷方式程序员相关命令 本文参考了博客园一篇帖子&#xff0c;ULR&#xff1a;cmd常用命令介绍(可收藏) - Mrwhite86 - 博客园 (cnblogs.com) CMD是Windows操作系统自带的命令行解释…

嵌入式C语言面试相关知识——内存管理(不定期更新)

嵌入式C语言面试相关知识——内存管理&#xff08;不定期更新&#xff09; 一、博客声明二、自问题目1、嵌入式系统的内存布局是怎么样的&#xff1f;2、动态内存分配在嵌入式系统中的使用有什么注意事项&#xff1f;3、什么是内存碎片&#xff0c;如何减少内存碎片&#xff1f…

恢复出厂设置后如何从 iPhone 恢复数据

在 iPhone 恢复出厂设置后&#xff0c;所有数据都会被删除&#xff0c;并且 iPhone 将恢复到原始出厂设置&#xff0c;这意味着您的所有 iPhone 数据&#xff0c;包括照片、视频、联系人和应用程序都将消失。 幸运的是&#xff0c;如果您有备份可以恢复&#xff0c;这并不一定…

Edge浏览器油猴插件的安装与使用

油猴 (又称篡改猴或Tampermonkey) 是最流行的浏览器扩展之一。它允许用户自定义并增强网页的功能。用户脚本是小型 JavaScript 程序&#xff0c;可用于向网页添加新功能或修改现有功能。使用油猴&#xff0c;您可以轻松在任何网站上创建、管理和运行这些用户脚本。 1.插件的安…

pycharm配置conda解释器

假如我新建了一个conda虚拟环境&#xff0c;名为python3.8

【数据结构与算法】快速排序霍尔版

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​

鸿蒙NEXT不再支持安卓这条路真的走的通吗?

前言 看到高赞又是一片嘲讽&#xff0c;“apk换种打包方式”等等轻松拿几百赞&#xff0c;我也是无语。 国内多家互联网大厂都已经启动HarmonyOS Next应用开发&#xff0c;预计明年正式上线&#xff0c;如今业内很多人都已经知道了。 网络上相关报道也有很多&#xff0c;新浪…

06.C2W1.Auto-correct

往期文章请点这里 目录 OverviewAutocorrectWhat is autocorrect?How it works Building the modelMinimum edit distanceMinimum edit distance algorithmMinimum edit distance Part 2Minimum edit distance Part 3 往期文章请点 这里 Overview 本周学习目标&#xff1a;…

Vue 使用 @click 绑定点击事件

https://andi.cn/page/621505.html

oracle数据库默认表空间详解

文章目录 oracle数据库默认表空间列表 oracle数据库默认表空间列表 系统表空间&#xff08;System Tablespace&#xff09; 系统表空间包含了系统级别的元数据&#xff0c;如数据字典、系统表和存储过程等。例如SYSTEM表空间用于保存数据库的数据字典、PL/SQL程序的源代码和解释…

通信协议_Modbus协议简介

概念介绍 Modbus协议&#xff1a;一种串行通信协议&#xff0c;是Modicon公司&#xff08;现在的施耐德电气Schneider Electric&#xff09;于1979年为使用可编程逻辑控制器&#xff08;PLC&#xff09;通信而发表。Modbus已经成为工业领域通信协议的业界标准&#xff08;De f…

04.C1W3.Vector Space Models

往期文章请点这里 目录 Vector Space ModelsWord by Word and Word by DocWord by Document DesignWord by Document DesignVector Space Euclidean DistanceEuclidean distance for n-dimensional vectors Euclidean distance in PythonCosine Similarity: IntuitionCosine S…

验证回文串-string题目

用双指针&#xff0c;left right从两头往中间对比&#xff0c;不是字母的都略过&#xff0c;比的时候化成小写字母 125. 验证回文串 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isPalindrome(string s) {if(s.size() < 1)return true;int left …

vue-org-tree搜索到对应项高亮展开

效果图&#xff1a; 代码&#xff1a; <template><div class"AllTree"><el-form :inline"true" :model"formInline" class"demo-form-inline"><el-form-item><el-input v-model"formInline.user&quo…

Git详细安装和使用教程

文章目录 准备工作-gitee注册认识及安装GitGit配置用户信息本地初始化Git仓库记录每次更新到仓库查看及切换历史版本Git忽略文件和查看文件状态Git分支-查看及切换Git分支-创建分支Git分支-合并及删除分支Git分支-命令补充Git分支-冲突需求: 准备工作-gitee注册 传送门: gite…