搜索分类总结
BFS(广度优先搜索) 和 DFS(深度优先搜索)
两个最基本的搜索,一个是按深度进行搜索,另一个是按广度进行搜索...
记忆化搜索(基于深搜)
就是用一个数组,dp[state] 表示state这个状态的结果,如果进行深搜时,发现已经得出dp[state]的结果了,就直接 return dp[state];
双向广搜
从初始结点和目标结点开始分别作两次BFS,每次选择队列中结点少的一边进行扩展,并且检测两边是否出现了状态重合
- //双向广搜代码框架
- struct State { }; //状态
- queue<State>que[2];
- bool vis[2];
- bool flag;
- void bfs(int d) {
- int size=que[d].size();
- while(size--) {
- //普通单广转移新状态v
- if(vis[d][v]) continue;
- if(vis[d^1][v]) {
- flag=true;
- return;
- }
- //新状态入队
- }
- }
- int dbfs() {
- //初始化
- int cnt=0;
- while(true) {
- cnt++;
- if(que[0].size()<que[1].size()) bfs(0);
- else bfs(1);
- if(flag) break;
- }
- return cnt;
- }
二分状态搜索
多半应用于背包的搜索,就是给你n<=30个物品,每个物品有一定的价值(Value<=10^9),问你将其分成两堆,使得两堆的总价值和差值最小是多少.
如果我们直接dfs搜索的话,复杂度会达到O(2^n).极限复杂度就是 2^30 !!!
我们可以先记录sum为所有物品的价值总和,然后先将前15个物品的所有组合状态用一个hash[state]记录下来,然后按价值排序.
再对后15个物品枚举组合状态,记其组合的价值为Val,那么我们在hash[]中二分查找一个状态state,使得Val+hash[state]最接近sum/2(就是总价值的一半)...然后最小差值就在 O( (2^15) * log2(2^15) ) + O( (2^15)*log2(2^15) ) [前15个状态排序+后15个状态二分的复杂度] 的时间内完美解决了...
启发式搜索
启发式合并,很好很强大.Nlog(N)的复杂度.
A*搜索
定义一个估计函数 G = g(x) + h(x) . 其中g(x)为你实际已经走过的距离,然后其重点就在 h(x) 的选择,比如迷宫搜索最短路时,可以选择 h(x)为到终点的曼哈顿距离.那么如果用BFS实现,那么用一个优先队列,使得每次增广时选择G小的先增广,这样搜最优解的希望更大一些.如果是DFS的话,假设我们目前已经搜到的最优解为Ans,那么我们再搜另一个状态他的G如果大于Ans,那么就没有必要再搜下去了,剪掉...
A*的关键就在h(x)的选择,一般选择的都是:离目标状态最少还要花费多少步数.(当然这并不是说这样是最好的h(x).只是普遍的选择...).根据不同的需要,你也可以定义一个更强大的h(x)来,让你的搜索快的飞起来~~~
IDA*搜索
就是从小到大(当然你也可以二分枚举)不断限制搜索的深度,然后去做DFS半的A*,当搜到一个解的时候,那就是最优解了(如果是二分的话,还需缩小深度继续搜).因为我们是从小到大枚举的...
h()函数:①曼哈顿距离
②魔方旋转类,每次如果会改变c个数字,那么
( 曼哈顿距离和+(c-1) )/c 作为估计函数.
反正就是当前状态到大最终状态最理想情况下最少需要多少步来当做估计函数h().
哈希方法:
朴素的哈希:将这9个数的排列转化为一个int范围的值,范围过大
散列表哈希:转化为一个int值后对大素数取模,然后线性解决冲突
map哈希: 直接把状态使用map进行hash,太慢了
字符串哈希:将状态转换成字符串,用字符串hash,可能会有冲突
形状哈希: 特殊点的位置及物体的形状进行hash. (例如:贪吃蛇,对其蛇头位置及弯曲形状进行hash.)
- const int M=1000007;
- unsigned int HASH(char *str)
- {
- int hash=0,i=0;
- while(str[i]) hash=hash*7+(*str++);
- return((hash&0x7FFFFFFF)%M);
- }
如: 1 3 4 5 7 6 8 9 2 其Hash值为
博弈树搜索(α-β剪枝) (极大极小过程搜索)
例题:给出一个n*n的棋盘(n<=8).
0和1两个玩家轮流操作,0先
0玩家在棋盘的空位上放置0,1玩家放置1
当棋盘放满时查询两个玩家最大连通块中棋子的个数
玩家得分为比对方多的棋子的个数
剪枝技巧:
1.预处理一些状态.(先搜一半)
2.先对状态进行排序,然后再搜索,可以以此作为剪枝的工具.(从大往小搜:容量,边的度)
3.记忆化搜索
4.从终点扩展到每个点(x,y).记录距离为len[x][y],作为A*的估计函数.
5.缩图后,再进行搜索.
6.流氓剪枝.(卡节点什么的)
7.正搜不行,进行反搜.
8.根据最优解应该具有的特性,按顺序搜索.
9.状态压缩(二进制,三进制,或者哈希不同形态...)
10.是否能将所有数据的目标状态转换成一样.