深搜-剪枝优化

news2024/11/17 23:40:45

目录

1.问题引入

2.知识讲解

【搜索术语】

(1) *可行性剪枝*:

(2) *预处理剪枝*:

(3) *重复性剪枝*:

(4) *最优性剪枝*:

(5) *顺序剪枝*:

3.例题解析

【例题1】工作分配问题。

【例题2】组合取数。


1.问题引入

通过前面深搜的学习,相信同学们已经掌握了搜索的基本思路。但有些问题直接搜索会得到部分分,并不会得到满分,而且分析其时间复杂度也很高。这时候就需要优化,提高搜索效率。

在之前的递归章节,已经介绍了一种优化方式——记忆化。本节重点学习剪枝的优化方式。

2.知识讲解

学习剪枝技巧之前,先来理解搜索中的一些术语,便于理解剪枝。

【搜索术语】

搜索树:从初始状态出发能访问的所有状态节点及对应路径构成的一棵树。

状态:各种属性,如位置、步数、总和、路径记录等等,有时通过参数记录,有时用全局变量记录。

回溯:是一种经常被用在深度优先搜索(DFS)的技巧。其基本思想是——从一条路往前走,能进则进,不能进则退回来,换一条路再试。典型的例题是:八皇后问题。

【剪枝】剪枝,顾名思义,就是通过一些判断,砍掉搜索树上不必要的子树。有时候,我们会发现某个结点对应的子树的状态都不是我们要的结果,那么我们其实没必要对这个分支进行搜索,砍掉这个子树,就是剪枝。

剪枝有很多种,下面介绍几种常用的剪枝:

(1) *可行性剪枝*:

在搜索过程中,一旦发现如果某些状态无论如何都不能找到最终的解,就可以将其“剪枝”了,比如越界操作、非法操作,也是用的最多的剪枝方法。一般通过条件判断来实现,如果新的状态节点是非法的,则不扩展该节点。例如:扫地机器人,不能超出边界的代码就属于可行性剪枝。

(2) *预处理剪枝*:

对于某些特殊的情况,可能不需要搜索或者搜索的数据量可以缩减,这时可以提前处理数据,便于加速后续搜索。一般在搜索之前预处理数据。

(3) *重复性剪枝*:

对于某一些特定的搜索方式,一个方案可能会被搜索很多次,这样是没必要的。在实现上,一般通过一个记忆数组来记录搜索到目前为止哪些状态已经被搜过了,然后在搜索过程中,如果新的状态已经被搜过了,则不再扩展该状态节点。例如:迷宫的第一条出路,就是把走过的路径标记掉,防止后面在错误的路径上反复搜索。

(4) *最优性剪枝*:

对于求最优解的一类问题,通常可以用最优性剪枝,比如在求解迷宫最短路的时候,如果发现当前的步数已经超过了当前最优解,那从当前状态开始的搜索都是多余的,因为这样搜索下去永远都搜不到更优的解。通过这样的剪枝,可以省去大量冗余的计算,避免超时。在实现上,一般通过状态变量或记忆数组来记录搜索到目前为止的最优解,然后在搜索过程中,如果新的状态已经不可能是最优解了,那再往下搜索肯定搜不到最优解,于是不再扩展该状态节点。

(5) *顺序剪枝*:

通常来讲,搜索的顺序可以不固定,算法可以进入搜索树的任意的一个子节点。但假如我们要搜索一个最小值,而非要从最大值存在的那个节点开搜,就可能存在搜索到最后才出解。而我们从最小的节点开搜很可能马上就出解。这就是顺序剪枝的一个应用。一般来讲,有单调性存在的搜索问题可以和贪心思想结合,进行顺序剪枝。例如:素数分解,可以发现分解方案从小到大具有单调性,采用贪心思想直接顺序搜索就可以得到答案。

通过上面概念的理解,会发现之前已经在很多例题中见到了不同剪枝的用法。下面再结合一些例题的思路和注释去理解剪枝的具体思路和做法。 

3.例题解析

【例题1】工作分配问题。

设有n件工作分配给n个人(1≤n≤20),将工作i分配给第j个人费用为Cij,为每个人分配一件不同的工作,对于给定的工作费用,计算最佳工作分配方案,使得总费用达到最小。第i行表示工作i分配给每个人需要的费用。

【问题分析】

此题问题类似于全排列问题,因为是n个不同的工作分配给不同的n个人,所以相当于n的全排列,但如果把所有的全排列列举一遍,肯定会超时。(不信的话,可以自己试一试之前的全排列问题需要多久才能运行出20的全排列)

优化方向:最优性剪枝。考虑到题目要求总费用最小,若当前的排列方案不符合之前求解的最小值,那么这种方案肯定不是最优的,提前剪枝。注意,不要在枚举完排列的时候判断最小,而是在过程中就可以判断了,也就是没选够n个工作就超过了之前最小的费用。 

参考代码如下:

#include <bits/stdc++.h>
using namespace std;
int n;
int a[21][21];
int f[21];  //f[i]标记给第i人了安排了工作
int ans = INT_MAX;
//sum记录当前的和,cnt当前是第几个人,同时也表示当前的总人数
void dfs(int sum, int cnt) {
    if (cnt == n + 1) { //当递归到n+1时,前n个人都已选好
        ans = sum;
        return;
    }
    for (int i = 1; i <= n; i++) {
        if (f[i] == 0 && ans > sum + a[cnt][i]) {  //需要访问&&最优性剪枝
            f[i] = 1; //标记已访问
            dfs(sum + a[cnt][i], cnt + 1);
            f[i] = 0; //取消标记
        }
    }
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> a[i][j];
    dfs(0, 1);
    cout << ans;
}

【例题2】组合取数。

给出n个正整数x1,x2,...,xn,在这n个数中任取r个,请你计算r个数的和为质数的个数。(其中1≤r≤n≤30,每个正整数xi≤105) 

解释:从5个数中任取3个的组合有10种。其中,只有8+12+9和13+7+9的和为质数29,所以答案为2种选法。

【问题分析】

这一题是选数类问题,标准的深搜代码。但阅读数据大小,会发现可能超时,超时的唯一原因就是搜索节点过多,那么如何优化呢?

优化方向一:采用可行性剪枝,减少无效搜索。在搜索的过程中,假如当前搜索到cur选择了cnt个数,那么还剩n-cur个数可以去选,还需要选择r-cnt个数。如果n-cur<r-cnt,那么就算后面所有的数都选择了,也无法达到目标状态。这样的情况我们就不需要去搜索了,可以剪枝。

优化方向二:采用预处理剪枝,提前减少搜索次数。本题是从n个数中选r个数,那么当r很大时,就容易因为递归过深导致超时;反向考虑一下,选r个数和不选n-r个数是等价的。如果r>n-r,那么选r个数就会比不选n-r个数的搜索深度要深。这时,我们去搜n-r个数来表示“不选”这些数,然后判断剩下的数的和是否是质数即可。

优化方向三:顺序剪枝。之前选过的数字不需要再次判断,可以用参数cur记录当前搜索的下标,之后每次选择的数字都是cur下标之后的数字。因此也不需要标记当前数字是否选择。

结合上述优化方向,参考代码如下: 

#include <bits/stdc++.h>
using namespace std;
int n, x[35], r, ans, tot;  // tot:所有数字总和
bool flag;  // 是否反算,默认不反算
bool prime(int n) {
    if(n < 2) return 0;
  for(int i = 2; i <= n / i; i++)
    if(n % i == 0) return 0;
  return 1;
}
void dfs(int cur, int sum, int cnt) {  //参数:当前下标、当前总和、当前个数
    if (cnt == r) {
        if (flag && prime(tot - sum)) ans++;  //反向算,14行
        else if (!flag && prime(sum)) ans++;
        return;
    }
    if (cur > n) return;
    for (int i = cur + 1; i <= n - r + cnt + 1; i++)  //19行
        dfs(i, sum + x[i], cnt + 1);
}
int main() {
    cin >> n >> r;
    if (r > n - r) {  //若搜索数字个数超过一半,那么就采取搜索不取的个数
        flag = true;
        r = n - r;
    }
    for (int i = 1; i <= n; i++) {  //提前计算所有数字的总和tot
        cin >> x[i];
        tot += x[i];
    }
    dfs(0, 0, 0);
    cout << ans;
    return 0;
}

说明:

(1) 解释一下14行代码的含义,flag为真说明需要反算,sum是反选的数字之和,tot-sum是正常选的数字之和,若正选的数字之和是质数,说明是一种取数的方案。

(2) 第19行准备搜索选择下一个数时,也可以优化,若选取的数字不足,则不需要搜索了。即i 最大为 n-r+cnt+1。 

请各位

 球球了三连(必回)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态规划IV (118、119、198、213、337)

CP118 杨辉三角 题目描述&#xff1a; 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 学习记录&#xff1a; 思想就是没有思想&#xff0c;的杨辉三角&#xff0c;但是注意resiz…

AUTOSAR MCAL之SPI(Specification of SPI Handler/Driver)

本文将详细介绍AUTOSAR MCAL SPI模块的知识点及注意事项&#xff0c;本模块的配置会在其他文章进行分享。本文大部分内容来源于标准&#xff0c;并参照了NXP S32K1系列的 MCAL SPI的代码。 耐心看完本文后&#xff0c;你就对AUTOSAR MCAL SPI有了非常深入的了解。 目录 1. 模…

在githhub上创建个人主页的方法【2023更新版】

01-进入github的网站&#xff0c;链接 https://github.com/ &#xff0c;然后注册&#xff0c;登陆&#xff0c;注意登陆时设置的用户名(username)就是将来你个人主页的三级域名&#xff0c;所以这里一定要慎重填写username。如下图所示&#xff1a; 02-注册完成后进入个人主…

【备战秋招】每日一题:5月13日美团春招第四题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;为了更好的阅读体检&#xff0c;&#xff0c;可以查看我的算法学习博客第四题-沙堡 在线评测链接:P1289 题目描述 塔子哥在海边建了一个沙堡乐园。 里面有一个巨大的沙堡&#xff0c;塔子哥每年都会增加这个沙堡的层数&#xff0c;但也有一定…

Found a swap file by the name “.credentials.swp“

问题 使用vim终端编辑一个文件的时候&#xff0c;遇到了一个提示&#xff1a;Found a swap file by the name ".credentials.swp" 解决 这是由于编辑文件意外退出导致的&#xff0c;产生了.swp文件,这个时候&#xff0c;只要按下键盘【A】键 然后&#xff0c;使用…

Easy_Trans轻松让你的项目减少30%SQL代码量

什么是Easy_Trans Easy Trans是一款用于做数据翻译的代码辅助插件&#xff0c;利用MyBatis Plus/JPA/BeetlSQL 等ORM框架的能力自动查表&#xff0c;让开发者可以快速的把ID/字典码 翻译为前端需要展示的数据。 easy trans的优点 功能多样 缓存支持 跨微服务翻译支持(User和…

蓝奥声核心技术分享 ——无线单火控制技术

1.技术背景 无线单火控制技术指基于对目标场景状态变化的协同感知而获得触发响应并进行智能决策&#xff0c;属于蓝奥声核心技术--边缘协同感知(EICS&#xff09;技术的关键支撑性技术之一。该项技术属于物联网边缘域的无线通信与智能控制技术领域。 对于不同智能应用场景&am…

安庆师范大学之计科-数据结构MOOC期末考试

单选 5分/题&#xff0c;共30题 1、在长度为n的顺序表的第i个位置上插入一个元素&#xff0c;i的合理取值范围是&#xff08; &#xff09;。 A.1≤i≤n B.任意正整数 C.i≥0 D.1≤i≤n1 正确答案&#xff1a;D 2‏、已知L是带表头结点单链表的头指针&#xff0c;摘除…

kubernetes operator解析

您是否想过站点可靠性工程 (SRE) 团队如何有效地成功管理复杂的应用程序&#xff1f; 在 Kubernetes 生态中&#xff0c;只有一个答案&#xff1a;Kubernetes Operators&#xff01; 在本文中&#xff0c;我们将研究它们是什么以及它们是如何工作的。 Kubernetes Operator 概念…

STM32 软件模拟SPI

STM32 软件模拟SPI 前言关于 SPISPI 协议软件模拟实现Driver_SPI.hDriver_SPI.c 前言 STM32库&#xff1a;标准函数库 测试环境&#xff1a;STM32F103系列 关于 SPI SPI 协议 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是由摩托罗拉…

Python ChatGPT API 新增的函数调用功能演示

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 OpenAI 重磅更新&#xff0c;API 添加函数调用能力&#xff0c;能处理更长上下文&#xff0c;价格又有所降低 … 知乎讨论&#xff1a;https://www.zh…

【超详细教学】Python制作迷宫小游戏教程

文章目录 前言1.首先确定迷宫的大小2. 定义迷宫的墙壁和通道3.定义迷宫的起点和终点4.定义迷宫的方向5. 生成一个空的迷宫6. 在迷宫中随机选择一个起点和终点7. 在迷宫中随机选择一个方向8. 检查一个位置是否在迷宫内9. 检查一个位置是否是墙壁10. 检查一个位置是否是通道11. 检…

工程师卓越之旅:技术文档怎么写

0.意义和价值 当前信息共享长期技术知识传承加深作者的理解和思考交付包括代码和技术文档 1.准备阶段 明确文档需求、受众和内容范围 2.调研阶段 对比有代表性的同类或相似的技术文档&#xff0c;建立大致框架收集相关信息&#xff0c;分析验证进行技术决策在文档中将每个…

HttpServletRequest对象中获取客户端IP地址

什么是HttpServletRequest对象 HttpServletRequest对象是Java Servlet规范中定义的一种接口&#xff0c;它封装了客户端请求的所有信息&#xff0c;例如请求头、请求参数、请求方法、请求URL等。在Java Web开发中&#xff0c;HttpServletRequest对象非常常用&#xff0c;可以用…

关于Java SSM框架的面试题

一、Spring面试题 1、Spring 在ssm中起什么作用&#xff1f; Spring&#xff1a;轻量级框架作用&#xff1a;Bean工厂&#xff0c;用来管理Bean的生命周期和框架集成。两大核心&#xff1a;1、IOC/DI(控制反转/依赖注入) &#xff1a;把dao依赖注入到service层&#xff0c;se…

程序替换原理

文章目录 一、程序替换 一、程序替换 程序替换用于将当前进程的用户空间的代码和数据全部替换为新程序的代码和数据&#xff0c;程序替换不会创建新进程&#xff0c;而是用当前进程执行新程序的代码&#xff0c;fork 创建子进程后&#xff0c;子进程默认执行的是父进程的代码&…

信创-大数据平台CPU架构支持

一、CDH和HDP、CDP CDP数据中心类似于CDH和HDP,直接安装在硬件服务器上,目前支持市面上主流的X86服务器,包括国内海光服务器&#xff0c; CDH不支持ARM 以上两种大数据平台都仅支持x86架构&#xff0c;早在几年期RedHat联手cloudera公司发表声明将推出64位ARM版&#xff0c;据…

【备战秋招】每日一题:4月29日美团春招第一题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;为了更好的阅读体检&#xff0c;&#xff0c;可以查看我的算法学习博客第一题-选修课 在线评测链接:P1266 题目内容 某大学一共有 n 门课程&#xff0c;编号为 1 ~ n &#xff0c; m 个学院&#xff0c;编号为1 ~ m 。最近开学季&#xff0c;…

剑指 Offer 53 - II: 0~n-1中缺失的数字

看到这道题的第一反应就是二分查找&#xff0c;由于是递增的所以二分查找所需的时间很短 &#xff0c;设置一个左&#xff0c;一个右&#xff0c;一个中间&#xff0c;如果判断吧不同需要想下前面是否一样&#xff0c;如果是那么就找到&#xff0c;不是再继续二分查找。 我的思…

【redis】redis的5种数据结构及其底层实现原理

文章目录 redis中的数据结构redis数据结构底层实现stringlisthashsetintset字典 zset跳表插入删除过程 redis中的数据结构 Redis支持五种数据类型&#xff1a;string&#xff08;字符串&#xff09;&#xff0c;hash&#xff08;哈希&#xff09;&#xff0c;list&#xff08;…