一.搜索算法的处理技巧
1.与回溯法结合
在 搜索算法中,我们简单了解了DFS与BFS的基本概念和基本模板。
在处理搜索问题时,我们会遇到这种情况,当前结点无法向纵深方向拓展,而题干要求我们搜索全部的解,此时我们就可以往回移动至最近的结点,以此结点拓展出其他的子结点,以找到新的解。这个操作过程就运用了回溯法。
回溯法提高解决问题的效率,经常与深度优先搜索结合使用。为了方便理解,下面给出一个例题来体现回溯法怎样优化我们的搜索过程。
题目:
任何一个大于1的自然数 n ,总可以拆分成若干个小于 n 的自然数自然数之和。
分析:
分析题目得,我们可以用DFS来搜索所有成立的和式。那么我们可以用一个数组的 a[ i ] 来存放一个解中第 i 层的子结点。
在本题中不要求顺序,我们将1+1+2与1+2+1看作一个和式。那么我们经历1->1->1->1后,该如何走1->1->2的解呢?这里就可以结合回溯法了。
总结来说,回溯可以理解为返回上一层的状态,即恢复现场;在本题中就体现为 s-=i 进入下一层结点,而回溯的操作就是 s+=i (返回上一层结点)。
代码:
#include<cstdio>
#include<iostream>
#include<cstdlib>
using namespace std;
int a[10001]={1},n,total;
void search(int,int);
void print(int);
int main()
{ cin>>n;
search(n,1); //将要拆分的数n传递给s
cout<<"total="<<total<<endl; //输出拆分的方案数
}
void print(int t)
{ cout<<n<<"=";
for (int i=1;i<=t-1;i++)
cout<<a[i]<<"+";
cout<<a[t]<<endl;
total++;
}
void search(int s,int t)
{ int i;
for (i=a[t-1];i<=s;i++)
if (i<n)
{ a[t]=i;//保存当前拆分的数i
s-=i; //s减去数i, s的值将继续拆分
if (s==0) print(t);
else search(s,t+1);
s+=i; //回溯:加上拆分的数,以便产分所有可能的拆分
}
}
2.与剪枝法法结合
剪枝,可以理解为剪去搜索树中的某些“枝条”(即结点及其扩展的其他结点)。通过判断枝条有无用处,剪去无用的枝条,就可以避免一些不必要的遍历过程。
剪枝法与搜索算法的结合主要体现在两方面:
1.用约束函数在扩展结点处剪除不满足约束条件的路径;
2.用限界函数剪去得不到问题的解或最优解的路径。
小明参加一个抢东西的电视节目。这个节目很有意思,一共有n个东西可以拿(n<50)每个参加活动的人不能拿重量超过m(m<2000)的东西,而每个东西都有它的价值v,有的高有的低,帮助小明安排要拿的东西,使总价值最高。
二、题型整理
- 棋盘问题:N皇后,解数独等。
例题链接:P1219 [USACO1.5]八皇后 Checker Challenge.
题意:在n*n的棋盘上放棋子,要求所有的棋子不在同一行,同一列和同一对角线上,问有多少种放法,并输出前三种情况。
题解:
1.用三个数组分别表示列、左下到右上的对角线,左上到右下的对角线的状态。
2.用深搜遍历棋盘,若合法则填入,同时标记这个空,再进行下一层递归,否则取消标记进行回溯。
- 组合问题:N个数里面按一定规则找出k个数的集合。
例题链接: P1036 [NOIP2002 普及组] 选数
题意:n个数中任选k个数相加求和(k<n),求所有组合中和为素数的有几种。
题解:
1.DFS遍历所有组合,用函数判断是否为素数,设total记录合法结果的个数。
2.一个最多20个数,每一次循环为1或0的循环,即表示这个数是否被选择。
3.元素满足三个的时候,用return来结束。
- 排列问题:N个数按一定规则全排列,有几种排列方式
时间问题先总结到这,后续会继续补充。