搜索
来源:互联网 发布:js如何设置translatey 编辑:程序博客网 时间:2024/04/30 02:24
这周是搜索的题目,写一点对搜索算法的介绍,希望能帮助大家。
【深搜】【IDS】【广搜】【双向广搜】【IDS】【A*】【IDA*】
背景题目:
1:http://acm.hdu.edu.cn/showproblem.php?pid=1242(深搜或广搜)
2 : http://poj.org/problem?id=1011(剪枝)
3:http://poj.org/problem?id=1077(IDS,A*,IDA*,
4
http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&category=13&problem=1122&mosmsg=Submission+received+with+ID+9223032( IDA*)
一:深度优先搜索。
深度优先搜索对应使用的数据结构是栈,也就是说深度优先搜索的迭代行非常好,每次都是用最新的节点去扩展。栈中始终只保留着一条路径的状态信息,因此使用的内存空间比较少。但是裸的深搜算法时间复杂度太高,经常要我们根据题目的信息加一些优化剪枝,有时是需要我们判断重复的状态。
//使用栈写
Int dfs(Start_state,deep){
}
使用递归(即使用系统的栈)
Int dfs(now_state,MAX_deep){
}
手写栈在内存消耗和时间上都要优于递归,如果写的递归返回栈溢出的信息或者超时,那就改成手写栈试试。
由于深搜是不见棺材不掉眼泪的,因此很容易走到很深而且没有解的地方,而又可能在很浅的地方又有解,为了避免这种问题,引入了IDS,迭代加深搜索,就是控制每次搜索的深度进行深搜:
For(deep =初始深度; deep< 最大深度;deep ++)dfs(start_state,deep);
虽然deep每次增加使我们又将所有状态重新搜索了一遍,但是由于深搜的时间复杂度是深度的指数级,因此前面的状态用的时间对当前深度影响不会很大。
二:广度优先搜索
广搜在求解问题时时间效率很高,尤其是求解最优性问题。但是由于我们要将在未到达目标状态前的所有状态都存下来,因此空间消耗太大,而且大部分问题每一层的节点数都是以指数级方式增长,因此循环队列也解决不了内存问题。广搜是用队列来实现的,而且在广搜被用到求解最优问题时,队列一定要关于某一关键字递增。
int bfs(){
}
}
例子.
题目1的题意是说,在一个n×M的网格里要从a走到r。‘.’是路,‘#’是敌人,走一步需要花费1,打一个敌人需要花费1,求从a走到r的最少花费。这个题目用深搜很好写,遇到’.’则花费加1,继续搜。遇到‘#’则花费加2,继续搜。但是这是最优化的问题,明显用广搜更加合适,而且如果这个题的数据加强到1000,深搜很有可能超时。对于之前遇到’#’不能走的问题,我们直接用一个队列,把由当前状态扩展出来的结点放到队列尾部即可,但是这用方法对于本题是不行的。为什么会出现这种情况呢,因为花费有两种,如果只是按时间顺序的话,先扩展的点不一定就是最小的花费,有可能从另一个地方到达他又更小的花费。而之前的问题里,花费只有一种,因此花费和时间是等价的。
想想上面提到的一句话“队列一定要关于某一关键字递增”此题的关键字就是花费,当我们把一个被扩展出来的节点放入队列是,一定要保证队列关于花费单调递增,也就是说我们每次拿出来的去扩展节点的点,必须是队列中花费最小的点。这样才能保证最优解的正确性。这里通常用单调队列或优先队列实现。
题目2是一道经典的剪枝的题目。剪枝这种东西灵活性很强,具体题目都不一样,大概经常用的有1:对原始数据做排序、打乱等预处理。2:当前解和最优解比较。3:分析剩下数据的特点。
1:判重。
由于棋盘转化成数字是9位,因此肯定要hash判重,否则复杂度太高。对于这种全排列问题。有一种很强的没有冲突的hash方式叫“康托展开”的东西。我们观察全排列,会发现对于每一个排列,都会有个独一无二的东西,那就是逆序数序列,例如:
数列
然后还有一个东西叫“变进制数”
我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为
若P =10就是10进制.它可以表示任何一个自然数。
变进制数是每个位置i,逢pi进一。
有这样一中变进制的方法:第1位逢2进1,第2位逢3进1,……,第n位逢n+1进1
它的表示形式为
也可以扩展为如下形式(因为按定义a0始终为0),以与p进制表示相对应
K = a0*0! + a1*1! + a2*2! + a3*3! + ... +an*n! (其中0 <= ai <=i)。
可以证明这种变进制在进位方面是正确的。而这个方法下,n个数的全排列的最大值只有
(N +1)!-1
因此。我们将棋牌的逆序数列和这个变进制结合起来,就可以用O(1)的速度为棋牌判重。
关于变进制的详细内容:http://bbs.chinaunix.net/viewthread.php?tid=1283459
【IDS】(迭代加深)
上边我们讲到了如何高效判重,下一步就是搜索了。对于棋牌这种问题,由于搜索方向的不同,很容易错过解而陷入很深的地方,因此我们使用IDS算法,迭代加深搜索,这样才可以。
【双向广搜】
双向广搜是为了减少广搜的内存,增加速度。从末状态和初状态同时出发进行扩展节点,并判断是否有交集,发生了交集就表示找到了解。每次不是机械的前面走一步,后面走一步,而是尽量找节点数少的那一方去拓展。判断交集用hash就行。
广搜的效果是一个三角形,而双向广搜的效果是三角形中的一个菱形。
【A*】(优先队列广搜)
之前的算法都是没有去管当前状态有什么特点就去盲目的扩展,最多就是在转移的时候做一些优化的手脚。A*算法是启发式搜索,我们去分析当前状态,为状态打分。这样搜索就变的更加理性了。有三个参数:H
当我们要扩展节点时,启发函数值越小的,相对来说对我们越有利,因此在广搜中加入启发函数,每次我们都给每个状态计算出启发函数值,然后重从列中找函数值最小的节点进行扩展。为了效率,这里要使优先队列。而且某些点的F是可能变化的(虽然h值不变,但是G值有可能变)因此A*算法不仅要求储存所有状态,还要修改,所以这里对hash或者数据结构要求比较高。因此的空间复杂的很高。
我们只是要求估计函数小于真实值,因此估价函数的范围还是很广的,如果是0那就是裸的广搜了,因此估价函数的好坏直接影响搜索的效率。
在数码问题中使用的估价函数是曼哈顿距离,即棋盘上每个数离他目标位置的距离之和。
更强版本的是曼哈顿距离值之和×4/3. 给每个数加一个权值算出的估价函数更接近真实值。我还不会。。。。。。。 谁知道希望能回帖。
题目3
【IDA*】(启发式迭代加深搜索)
由于A*算法对空间复杂度极高,因此很多问题用不了A*。题目三是15数码问题。16个数字的棋局,用变进制的方法也没办法判重,因此双向广搜和A*算法是个用不了的,只能试试IDS了。但是IDS的效率又很低,只是盲目的搜索,对与15数码16!种状态的搜索,肯定不行。于是一个叫IDA*的东西出现了,它中和A*和IDS的两个优点,利用A*估计出来的最小步数进行剪枝,也就是说如果当前状态的最小步数大于我们限制的最大步数,则不在从这个节点继续搜索。利用IDS不用判重的优点,每次只管搜下一个便可。因此IDA
Int dfs(intdeep){//深度限制
If(H(temp) + G(temp) > deep)continue;
}
由于对搜索不熟,因此漏洞百出,欢迎批评指正。
由于长期聊QQ,所以错字较多,欢迎批评指正。
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- 搜索
- hdu3068 manacher算法 最长回文子…
- bupt204 北邮多校J题 后最数组+LC…
- poj3370 poj2356 鸽巢定理
- acm网络选拔赛原则和晋级规则
- 关于全局变量的若干细节(转…
- 搜索
- 30-JavaScript-事件-同一事件多个处理程序-window的事件-关闭右键菜单
- linux下的简易有道字典
- hdu3247自动机+TSP
- IOS , plist 配置项说明
- poj3621 0/1分数规划
- 有意思
- hdu 1394
- 感恩福州赛区