【基础算法】DFS中的剪枝与优化

作者:让我们一起加油好吗日期:2025/11/2

文章目录

  • 上文链接
  • 一、剪枝与优化
    • 1. 排除等效冗余
    • 2. 可行性剪枝
    • 3. 最优性剪枝
    • 4. 优化搜索顺序
    • 5. 记忆化搜索
  • 二、OJ 练习
    • 1. 数的划分
      • (1) 解题思路
        • (2) 代码实现
    • 2. 小猫爬山
      • (1) 解题思路
        • (2) 代码实现

上文链接

一、剪枝与优化

剪枝,形象地看,就是剪掉搜索树的分支,从而减小搜索树的规模,排除掉搜索树中没有必要的分支,优化时间复杂度。 在深度优先遍历中,有几种常见的剪枝方法:

1. 排除等效冗余

如果在搜索过程中,通过某一个节点往下的若干分支中,存在最终结果等效的分支,那么就只需要搜 索其中一条分支。

2. 可行性剪枝

如果在搜索过程中,发现有一条分支是无论如何都拿不到最终解,此时就可以放弃这个分支,转而搜索其它的分支。

3. 最优性剪枝

在最优化的问题中,如果在搜索过程中,发现某一个分支已经超过当前已经搜索过的最优解,那么这个分支往后的搜索,必定不会拿到最优解。此时应该停止搜索,转而搜索其它情况。

4. 优化搜索顺序

在有些搜索问题中,搜索顺序是不影响最终结果的,此时搜索顺序的不同会影响搜索树的规模。 因此,应当先选择一个搜索分支规模较小的搜索顺序,快速拿到一个最优解之后,用最优性剪枝剪掉别的分支。

5. 记忆化搜索

记录每一个状态的搜索结果,当下一次搜索到这个状态时,直接找到之前记录过的搜索结果。 记忆化搜索,有时也叫动态规划。


二、OJ 练习

1. 数的划分

【题目链接】

[P1025 NOIP 2001 提高组] 数的划分 - 洛谷

【题目描述】

将整数 n nn 分成 k kk 份,且每份不能为空,任意两个方案不相同(不考虑顺序)。

例如: n = 7 n=7n=7, k = 3 k=3k=3,下面三种分法被认为是相同的。

1 , 1 , 5 1,1,51,1,5;
1 , 5 , 1 1,5,11,5,1;
5 , 1 , 1 5,1,15,1,1.

问有多少种不同的分法。

【输入格式】

n , k n,kn,k ( 6 < n ≤ 200 6<n \le 2006<n≤200, 2 ≤ k ≤ 6 2 \le k \le 62≤k≤6)

【输出格式】

1 11 个整数,即不同的分法。

【示例一】

输入

17 3
2

输出

14
2

【说明/提示】

四种分法为:
1 , 1 , 5 1,1,51,1,5;
1 , 2 , 4 1,2,41,2,4;
1 , 3 , 3 1,3,31,3,3;
2 , 2 , 3 2,2,32,2,3.

【题目来源】

NOIP 2001 提高组第二题


(1) 解题思路

我们一共要把数分成 k kk 份,那么每一份我们选择从 1 11 一直枚举到 n nn,当枚举了 k kk 份时,就是递归终止条件。如果此时的和正好为 n nn,那么就统计一次次数。

但是显然我们不能每一个数都从 1 11 枚举到 n nn ,这样必然会超时,因此我们需要剪枝

**剪枝一:**由于顺序不同的序列我们认为是同一个序列,所以如果我们枚举的前一个数是 m mm,那么下一个数我们就应该从 m mm 开始枚举。剪掉 [ 1 , m − 1 ] [1 , m-1][1,m−1] 的所有分支。

**剪枝二:**比如说 n = 7 n = 7n=7, k = 3 k = 3k=3 时,当我们枚举到 [ 1 , 6 , _ _ ] [1,\ 6,\ \_\_\ ][1, 6, __ ] 的时候,还有必要再枚举第三个数吗?显然是没必要的,因为前两个数的和已经等于 7 77 了,再往后枚举的结果永远不可能等于 7 77。因此我们此时就要把这种情况剪掉。


(2) 代码实现

1#include<iostream>
2
3using namespace std;
4
5int n, k;
6int path, cnt;
7
8void dfs(int pos, int begin)
9{
10    if(pos == k)
11    {
12        if(path == n) cnt++;
13        return;
14    }
15    
16    // 剪枝二: 可行性剪枝
17    // 如果把剪枝放在这里的话那么递归就会进入到不合法的情况中再判断,就会超时
18    // if(path + begin * (k - pos) > n) return;
19
20    for(int i = begin; i <= n; i++)
21    {
22        // 剪枝二: 可行性剪枝
23        if(path + i * (k - pos) > n) return;
24        
25        path += i;
26        dfs(pos + 1, i);
27        path -= i;
28    }
29}
30
31int main()
32{
33    cin >> n >> k;
34
35    dfs(0, 1);
36    
37    cout << cnt;
38
39    return 0;
40}
41

2. 小猫爬山

【题目链接】

P10483 小猫爬山 - 洛谷

【题目描述】

Freda 和 rainbow 饲养了 N ( N ≤ 18 ) N(N\le 18)N(N≤18) 只小猫,这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了

Freda 和 rainbow 只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W WW,而 N NN 只小猫的重量分别是 C 1 , C 2 , … C N C_1,C_2,\dots C_NC1​,C2​,…CN​。当然,每辆缆车上的小猫的重量之和不能超过 W ( 1 ≤ C i , W ≤ 1 0 8 ) W(1\le C_i,W \le 10^8)W(1≤Ci​,W≤108)。每租用一辆缆车,Freda 和 rainbow 就要付 1 11 美元,所以他们想知道,最少需要付多少美元才能把这 N NN 只小猫都运送下山?

【输入格式】

第一行包含两个用空格隔开的整数, N NN 和 W WW。
接下来 N NN 行每行一个整数,其中第 i + 1 i+1i+1 行的整数表示第 i ii 只小猫的重量 C i C_iCi​。

【输出格式】

输出一个整数,最少需要多少美元,也就是最少需要多少辆缆车。

【示例一】

输入

15 1996
21
32
41994
512
629
7

输出

12
2

(1) 解题思路

**搜索策略:**依次处理每一只猫,对于每一只猫,我们都有两种处理方式:

  • 要么把这只猫放在已经租好的缆车上;
  • 要么重新租一个缆车,把这只猫放上去。

剪枝一:

在搜索过程中,我们用全局变量记录已经搜索出来的最小缆车数量。如果当前搜索过程中,已经用的缆车数量大于全局记录的最小缆车数量,那么这个分支一定不会得到最优解,剪掉。

剪枝二:

  • 优化枚举顺序一:从大到小安排每一只猫
    • 重量较大的猫能够快速把缆车填满,较快得到一个最小值;
    • 通过这个最小值,能够提前把分支较大的情况提前剪掉。
  • 优化枚举策略二:先考虑把小猫放在已有的缆车上,然后考虑重新租一辆车
    • 因为如果反着来,我们会先把缆车较大的情况枚举出来,这样就起不到剪枝的效果了。

(2) 代码实现

1#include<iostream>
2#include<algorithm>
3
4using namespace std;
5
6const int N = 20;
7int c[N];
8int s[N]; // 每一辆车目前的总重
9int n, w;
10int cnt;  // 当前用了多少辆缆车
11int ret = N;  // 全局最优解
12
13void dfs(int pos)
14{
15    // 最优性剪枝
16    if(cnt >= ret) return;
17
18    if(pos > n)
19    {
20        ret = cnt;
21        return;
22    }
23
24    // 优化搜索顺序
25    // 先安排在已有的车,再重开一辆车
26    for(int i = 1; i <= cnt; i++)
27    {
28        if(s[i] + c[pos] > w) continue;
29        s[i] += c[pos];
30        dfs(pos + 1);
31        s[i] -= c[pos];
32    }
33
34    // 重开一辆车
35    cnt++;
36    s[cnt] = c[pos];
37    dfs(pos + 1);
38    s[cnt] = 0;
39    cnt--;
40}
41
42
43int main()
44{
45    cin >> n >> w;
46    for(int i = 1; i <= n; i++) cin >> c[i];
47
48    sort(c + 1, c + 1 + n, greater<int>());
49
50    dfs(1);
51
52    cout << ret;
53
54    return 0;
55}
56

【基础算法】DFS中的剪枝与优化》 是转载文章,点击查看原文


相关推荐


Python 的内置函数 exec
IMPYLH2025/10/30

Python 内建函数列表 > Python 的内置函数 exec Python 的内置函数 exec 是一个强大的动态执行工具,它允许程序在运行时执行以字符串形式提供的 Python 代码。 def eval(source:str|codeobject, /, globals:dict=None, locals:mapping=None): ''' 执行表达式并返回结果 :param source: Python 表达式 :param globals :


面经分享——字节前端一面
Moment2025/10/28

最近在使用 NestJs 和 NextJs 在做一个协同文档 DocFlow,如果感兴趣,欢迎 star,有任何疑问,欢迎加我微信进行咨询 yunmz777 1. 使用的 React 版本? React 版本演进的趋势是怎样的? React 的版本迭代趋势体现了其向更高效、更简洁的开发体验不断发展的方向。从 React 16 开始,React 引入了许多新特性,如错误边界(Error Boundaries)和 Fiber 架构,显著提高了渲染效率。React 17 主要是稳定性的更新,并没有引入


Redis(83)Redis的缓存击穿是什么?
Victor3562025/10/25

缓存击穿的概念 缓存击穿(Cache Breakdown)指的是在某一个热点缓存数据过期的瞬间,有大量并发请求同时访问这个数据,而该数据在缓存中不存在,因此所有的请求都打到数据库上,导致数据库压力过大,可能引起系统性能问题。 解决缓存击穿的方法 为了解决缓存击穿问题,可以采取以下策略: 互斥锁(Mutex):在缓存失效时,只有一个线程去加载数据,其他线程等待。 永不过期:热点数据的缓存永不过期,只在数据更新时主动去更新缓存。 预加载:在缓存即将过期之前,提前加载数据到缓存。 以下是这几种解决


从入门到精通:JavaScript异步编程避坑指南
良山有风来2025/10/23

你是不是也遇到过这样的场景?页面上有个按钮,点击后需要先请求数据,然后根据数据更新界面,最后弹出提示框。结果代码写着写着就变成了“回调地狱”,一层套一层,自己都看不懂了。更可怕的是,有时候数据没加载完,页面就显示了,各种undefined错误让人抓狂。 别担心,这篇文章就是来拯救你的。我会带你从最基础的异步概念开始,一步步深入Promise、async/await,最后还会分享几个实战中超级好用的技巧。读完本文,你不仅能彻底理解JavaScript的异步机制,还能写出优雅高效的异步代码。 为什么


Swift 字符串与字符完全导读(三):比较、正则、性能与跨平台实战
unravel20252025/10/22

字符串比较的 3 个层次 比较方式API等价准则复杂度备注字符相等“==”扩展字形簇 canonically equivalentO(n)最常用前缀hasPrefix(:)UTF-8 字节逐段比较O(m)m=前缀长度后缀hasSuffix(:)同上,从后往前O(m)注意字形簇边界 示例 let precomposed = "café" // U+00E9 let decomposed = "


主流DDS实现简介及对比
^Moon^2025/10/20

DDS有多个团体进行过实现,这些实现各有侧重,适用于不同场景(如嵌入式、实时系统、大规模分布式系统等)。以下从开源属性、性能、功能、适用场景等维度进行对比分析: 一、主流DDS实现简介及对比 特性RTI Connext DDSFast DDSADLINK OpenSplice DDSCycloneDDS开发者Real-Time Innovations (RTI)eProsima(西班牙公司)ADLINK Technology(台湾凌华)Eclipse基金会(开源社区)开源属性商业闭源(提供免


Anthropic Haiku 4.5:这波AI性能,我愿称之为“超值”!
墨风如雪2025/10/19

嘿,各位AI圈的朋友们!最近,Anthropic又悄悄地扔出了一颗重磅炸弹——他们最新发布的Claude Haiku 4.5,可不是那种哗众取宠的“大而全”模型,它走的是一条“小、快、灵”的路线,但其带来的性价比和实用性,绝对能让你眼前一亮。在我看来,这不只是一次版本更新,更是AI普惠化进程中一个非常重要的里程碑。 想象一下,你用着一台小型跑车的钱,却买到了一辆豪华轿车的核心动力,甚至速度还更快——Claude Haiku 4.5给人的,就是这样一种惊喜。 小身材,大能量:性能直逼“老大哥” H


Docker快速入门——第四章Docker镜像
温柔一只鬼.2025/10/18

传送门: Docker快速入门——第一章Docker入门 Docker快速入门——第二章Docker基本概念 Docker快速入门——第三章Docker环境安装 一、搜索镜像 在Docker中,通过如下命令搜索镜像: docker search [OPTIONS] TERM 其中TERM是你要搜索的镜像关键词 常用选项(OPTIONS): --limit N:限制返回结果的数量(默认为25,最大为100) --filter"is-oddicial=true":只


【搞发🌸活】不信书上那套理论!亲测Javascript能卡浏览器Reader一辈子~
大怪v2025/10/16

点进来的前端佬,先别走! 让我详细给你逼逼叨! 在很久很久以前,前端圈就广泛流传,Javascript的加载和执行,都会阻塞浏览器Render。 然后过了这些日子,作为一名优秀的前端佬的意识爆发。 按照上面的说法,那是不是可以构造一个Javascript程序,让后续的CSS以及HTML文本永远都不能被解析Render到? 喔,觉的挺来劲的,说干就干! 前言 一开始构建了这么一个HTML,如下: <!DOCTYPE html> <html> <head> <meta charset="UT


算法刷题-数组篇之螺旋矩阵II(超简单)
destiny_tool2025/10/15

力扣题目链接https://leetcode.cn/problems/spiral-matrix-ii/ 1.1 问题描述: 给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 示例: 输入: 3 输出: [ [ 1, 2, 3 ],                        [ 8, 9, 4 ],                        [ 7, 6, 5 ] ]    1.2 思路: 本题具体考察

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0