浅谈深搜剪枝优化

一、引入

在《深搜与栈浅谈》中,我曾提到过深搜一般效率很低,因此需要优化。这里我给大家简单介绍一下深搜的常见剪枝方法、迭代加深思想以及双向搜索的思路。最后以几道比较经典的例题讲解一下实际题目中的优化。

二、深搜的剪枝

在深搜每次扩展中,我们到达一个新的决策点B,在访问完B及其所有子决策点后,便回到其父决策点A。当决策点没有重复时,所有的父子关系A->B便构成了一棵树。有重复时,我们也一样把它看成一棵树。在遍历中,如果发现沿着某一条路径无法到达目的时,便可以提前退出。形象地可以把这种提前退出搜索的方法看成把树中的某一条树枝“剪掉”。选取合适的剪枝可以大大提高搜索效率。如果选用不正确的剪枝,可能会导致错误。因此选用合理的剪枝十分重要。

像这样一棵搜索树,在在到某个决策点时,如果剪枝正确,就会大大提升效率。
在这里插入图片描述
如果采用错误的剪枝,就会出现下图情况,找出错误答案或判断为无解。
在这里插入图片描述

1.优化搜索顺序
对于某些过程,按照某些顺序能更方便快捷地解决问题。一般地可以将元素从小到大或从大到小排序,再在每个循环中逐个枚举。
2.可行性剪枝
在某种状态,可以通过数学的方法判断沿着这条路径是否能到达终点。如果不能,那就直接退出。
3.上下界剪枝
再循环中,如果发现到达某一个值之前或之后都是无解的,就可以不枚举那些值。不难发现,上下界剪枝是一种特殊的可行性剪枝。
4.排除等效冗余
有时后,先解决A后解决B与先解决B后解决A是等效的,这样便把一个过程重复执行了两遍,因此可以在枚举到后者时直接跳过。一般地,通过自行加一些限制(如限制从小到大取值)达到这个剪枝。
5.最优性剪枝
如果当前总代价已经超出了之前找到的最小代价,直接退出。
一般要根据实际情况选择合适的剪枝,下面1、2两道题是关于深搜剪枝的。

三、迭代加深

考虑下面这幅图的情况:
在这里插入图片描述

其中蓝色结点表示当前路径,红色表示终点。然而终点的深度非常小,有的路径扩展出来的结点深度大,这样就会影响效率。
在这里插入图片描述
但如果我们限制深度搜索,(这里标蓝的点为访问终点前所有要访问的点)尽管每个深度都要搜一遍,但在判断终点深度足够小的前提下,可以避免一直往深度大且错误的路径寻找,从而大大提高效率。

四、双向搜索

在起点和终点都确定的前提下,可以采用双向搜素。
分别从起点和终点出发,按照一定深度扩展出所有结点。当我们发现从起点扩展出来的结点与终点扩展出来的结点可以互相到达时,便能够找到正确路径。
在这里插入图片描述
图中,两个小圆分别表示起点和终点,大圆表示分别扩展出来的结点的集合。其中中间重叠部分的结点可以相互到达,这样便能找到正确路径(绿色那条)。而两个大圆之外的部分便不用枚举(即不用走类似上图的所有红色路径),这样就可以大大提升效率。

五、例题


1.数的划分

将整数n分成k份,且每份不能为空,任意两个方案不相同(不考虑顺序)。问有多少种不同的分法。

用dfs(rst,tot)表示把数rst分成tot所需方案数,如果枚举i表示这次取出的数,那么,dfs可以由所有的dfs(rst-i,tot-1)得到。但这样效率不高,我们考虑剪枝。
剪枝一:(排除等效冗余)如果在rst中要取a和b两个数(a+b<=rst),那么先取那个数都是一样的,因此要限制从小到大取数。记录lst表示上一次取的数。
剪枝二:(上下界剪枝)如果当前取的数是i,根据剪枝一,后面取的数最小不能小于i,即后面的数总和不小于itot。此时应有itot<=rst才能保证得出正确答案。
事实上,这个过程中有重复的结点,故还可以加上记忆化。

#include<iostream>
#include<cstdio>
using namespace std;
int n,k;
int dfs(int rst,int tot
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值