热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

浅谈深搜剪枝优化

文章目录一、引入二、深搜的剪枝三、迭代加深四、双向搜索五、例题1.数的划分2.小木棍一、引入在《深搜与栈浅谈》中,我曾提到过深搜一般效率很低,因此需要

文章目录

  • 一、引入
  • 二、深搜的剪枝
  • 三、迭代加深
  • 四、双向搜索
  • 五、例题
    • 1.数的划分
    • 2.小木棍



一、引入

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


二、深搜的剪枝

在深搜每次扩展中,我们到达一个新的决策点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)得到。但这样效率不高,我们考虑剪枝。
剪枝一&#xff1a;&#xff08;排除等效冗余&#xff09;如果在rst中要取a和b两个数&#xff08;a&#43;b<&#61;rst&#xff09;&#xff0c;那么先取那个数都是一样的&#xff0c;因此要限制从小到大取数。记录lst表示上一次取的数。
剪枝二&#xff1a;&#xff08;上下界剪枝&#xff09;如果当前取的数是i&#xff0c;根据剪枝一&#xff0c;后面取的数最小不能小于i&#xff0c;即后面的数总和不小于itot。此时应有itot<&#61;rst才能保证得出正确答案。
事实上&#xff0c;这个过程中有重复的结点&#xff0c;故还可以加上记忆化。

#include
#include
using namespace std;
int n,k;
int dfs(int rst,int tot,int lst){if(tot&#61;&#61;0&&rst&#61;&#61;0) return 1; //已经取了k个数且n被取完&#xff0c;成功if(tot&#61;&#61;0&&rst!&#61;0) return 0; //已经取了k个数但n未取完&#xff0c;失败int ans&#61;0;for(int i&#61;lst;i*tot<&#61;rst;i&#43;&#61;1){ //限制范围枚举ians&#43;&#61;dfs(rst-i,tot-1,i);}return ans;
}
int main(){scanf("%d%d",&n,&k);printf("%d\n",dfs(n,k,1)); //有时候求方案数要加上long longreturn 0;
}



2.小木棍


乔治拿来一组等长的木棒&#xff0c;将它们随机的砍掉&#xff0c;得到若干根小木棍&#xff0c;并使每一节小棍的长度都不超过50个单位。然后他又想把这些木棍拼接起来&#xff0c;恢复到裁剪前的状态&#xff0c;但他忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序&#xff0c;帮助乔治计算木棒的可能最小长度&#xff0c;每一节木棍的长度都用大于零的整数表示。


先枚举木棒长度&#xff0c;再深搜判断是否可以取到。以vi[i]表示木棍i是否被用过&#xff0c;dfs(now,tot)表示目前拼出长度为now的木棒且已经用了tot根木棍的方案数。如果下一个要拼上木棍编号为i&#xff0c;则转化为求解dfsd(now&#43;a[i],tot&#43;1)&#xff0c;同时标记vi[i]&#61;1。当now&#61;&#61;len时&#xff0c;令now&#61;0即重新拼另一根木棒即可。
剪枝一&#xff1a;&#xff08;优化搜索顺序&#xff09;将木棍从大到小排序。根据贪心先把用处小的长木棍先拼了。
剪枝二&#xff1a;&#xff08;排除等效冗余&#xff09;当一根木棒拼法确定&#xff0c;拼的顺序无所谓&#xff0c;那么限制从大到小取木棍。
剪枝三&#xff1a;&#xff08;可行性剪枝&#xff09;如果新木棒在第一轮中拼接失败&#xff0c;也就是说当前剩下最长的木棍取不了&#xff0c;这就意味着它永远取不到&#xff0c;那么直接返回失败。
剪枝四&#xff1a;&#xff08;可行性剪枝&#xff09;如果刚拼完一根木棒后&#xff0c;搜索失败&#xff0c;直接退出。那是因为在接下来如果找到另一种拼完当前木棒的方法&#xff0c;那么长的木棒被保留了下来。既然在较短的木棒无法找到合适的拼法&#xff0c;那么较长的木棒更找不到合适的拼法了。
剪枝五&#xff1a;&#xff08;排除等效冗余&#xff09;如果下一步拼接长度为a[i]的木棒&#xff0c;但最终结果是失败时&#xff0c;直接跳过所有长度为a[i]的木棒。我们以数组v[i]记录当前情况中长度为i的木棒是否已被取过。

#include
#include
#include
using namespace std;
int n,m,a[66],vi[66];
bool cmp(int x,int y){return x>y;
}
int dfs(int now,int tot,int len,int lst){int v[51];for(int i&#61;1;i<&#61;n;i&#43;&#61;1) v[a[i]]&#61;0;if(tot&#61;&#61;n) return 1;if(now&#61;&#61;len) now&#61;0,lst&#61;0;for(int i&#61;lst&#43;1/*剪枝二*/;i<&#61;n;i&#43;&#61;1){if(vi[i]||v[a[i]]) continue; //剪枝五if(now&#43;a[i]<&#61;len){ //可以拼vi[i]&#61;1;v[a[i]]&#61;1;if(dfs(now&#43;a[i],tot&#43;1,len,i)) return 1;vi[i]&#61;0;if(now&#61;&#61;0||len-now&#61;&#61;a[i]) break; //剪枝三、四}}return 0;
}
int main(){scanf("%d",&n);for(int i&#61;1;i<&#61;n;i&#43;&#61;1){scanf("%d",&a[i]);m&#43;&#61;a[i];}sort(a&#43;1,a&#43;n&#43;1,cmp); //剪枝一for(int i&#61;1;i<&#61;m;i&#43;&#61;1){if(m%i!&#61;0) continue; //所有木棒总长度应该能被木棍长度整除if(dfs(0,0,i,0)){printf("%d\n",i); //第一个找到的便是最优解break;}}return 0;
}



推荐阅读
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
author-avatar
木棉
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有