剪枝算法的思路

该文章已生成可运行项目,

剪枝算法:优化枚举与递归的有效策略

在解决一些组合问题、搜索问题或者图论问题时,暴力枚举和递归搜索的时间复杂度常常非常高。为了减少不必要的计算,我们可以采用剪枝算法,在搜索过程中有意识地提前“剪掉”那些不可能产生有效解的路径,从而提高算法效率。

在本文中,我们将以NOIP 2001 提高组 P1025 题目——数的划分为例,讲解剪枝算法的原理、思路,并通过图解阐述如何应用剪枝来优化搜索。

1. 案例分析

案例描述
将一个整数 n n n 分成 k k k 份,每份不能为空,且每份的和等于 n n n,要求计算不同的分法数目。需要注意的是,两个分法如果数字出现的顺序不同,不认为是不同的分法。

例如:

  • n = 7 n = 7 n=7, k = 3 k = 3 k=3,四种合法的分法是:
    • 1 , 1 , 5 1,1,5 1,1,5
    • 1 , 2 , 4 1,2,4 1,2,4
    • 1 , 3 , 3 1,3,3 1,3,3
    • 2 , 2 , 3 2,2,3 2,2,3

我们可以将这道题转化为一个整数划分问题,其中每个划分的和为 n n n,且每个划分包含 k k k 个数字。题目的难点在于如何有效地枚举所有可能的划分,并确保不重复计算。

2. 剪枝算法原理

剪枝的核心思想是,在递归或枚举过程中,如果我们能够确定某条路径不会导致有效解,就可以直接停止继续探索该路径,从而避免不必要的计算。

例如,在本题中,如果我们已经得到了前 k − 1 k-1 k1 个部分的和,并且当前的部分和已经超过了 n n n,那么就可以停止递归,因为不可能再通过增加其他数值得到合适的解。

具体到这道题的剪枝思路,可以归纳为以下几种:

  1. 递归中止条件

    • 如果已经选择的数的个数达到了 k k k,且剩余的部分和为零,说明找到了一个合法的划分。
    • 如果已经选择的数的个数大于 k k k 或者剩余的和小于零,说明当前路径无效,剪枝。
  2. 剪枝策略

    • 由于题目要求每个分法的数值必须满足“递增”或“不降序”的条件(例如, 1 , 1 , 5 1, 1, 5 1,1,5 5 , 1 , 1 5, 1, 1 5,1,1 被认为是相同的),在递归时可以限制每次选择的数不小于前一个数,从而避免重复的分法。

3. 剪枝的思路与算法设计

3.1 递归设计

我们可以通过递归的方式来枚举每种分法。在递归过程中,我们依次选择每个数,并逐步递减剩余的和。

递归的基本思路是:

  • 初始时,我们要把 n n n 分成 k k k 份。
  • 每次递归时,选一个数,并且递归剩余的部分。
  • 在递归中,我们要保证每次选择的数不小于之前选择的数,以避免重复。
3.2 剪枝的应用

在递归过程中,如果当前的数值加上已经选择的数值总和已经大于 n n n,则可以立即返回,剪掉这条不可能产生有效解的路径。此外,如果分成的份数已经达到了 k k k,并且剩余和正好为0,则当前分法合法,计算为一种有效的划分。

4. 图解思路

假设 n = 7 n = 7 n=7 k = 3 k = 3 k=3,我们从1开始选择数,每次递归选择下一个数。递归树的每一层对应选择一个数,叶子节点对应最终的划分。

                 [7, 3]                     <-- 初始状态,剩余和为7,需分成3份
                /     \
             [6, 2]   [5, 2]
             /   \      /   \
           [5, 1] [4, 1] [3, 1] [2, 1]
           /  \        /  \
         [4, 0]  [3, 0]   ...

在递归过程中,通过剪枝,我们会在部分路径上立即回退,避免了冗余的计算。

5. python代码实现

洛谷剪枝题目:数的划分为例

ddef dfs(cnt, u, path):
    global res, ans, n, k
##    print(f"cnt is:{cnt}, u is:{u}, res is:{res}, path is:{path}")
    res += u
    if res == n and cnt == k: # 必须拆分成K个 !!!!
        ans.add(str(sorted(path))) # 路径去重
##        print("Yes")
##        print(f"ans is:{ans}")
        return
    for v in range(1, n + 1):
        if cnt + 1 <= k and res + v <= n: # 满足条件进入递归分支
            path.append(v) # 扩展路径
            dfs(cnt + 1, v, path)
            res -= v # 退出分支还原场景
            path.pop() # 还原路径

n, k = map(int, input().split())
ans = set()
res = 0
dfs(0, 0, [])
##for ele in ans:
##    print(ele)
print(len(ans))

在这里插入图片描述

由于使用的py,评测超时,大家可以用更快的编程语言如:C、C++

5. 算法分析与优化

  1. 递归深度:本算法的递归深度为 k k k,即最多递归 k k k 次。
  2. 剪枝的效果:剪枝主要体现在避免重复计算和不可能的路径。通过递归中每一步限制当前选择的数大于等于上一个选择的数,减少了不必要的搜索。

7. 总结

通过剪枝技术,我们能够在解决类似“数的划分”问题时显著提高效率。利用递归搜索和剪枝相结合,能够避免重复计算和不可能的分法,从而减少不必要的计算时间。这种方法在组合优化、动态规划以及图算法中都有广泛的应用。

对于这类问题,正确地设计递归树、合理剪枝是提升算法性能的关键。

本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SweetCode

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值