搜索

来源:互联网 发布:js如何设置translatey 编辑:程序博客网 时间:2024/04/30 02:24

这周是搜索的题目,写一点对搜索算法的介绍,希望能帮助大家。

【深搜】【IDS】【广搜】【双向广搜】【IDS】【A*】【IDA*

背景题目: 

1http://acm.hdu.edu.cn/showproblem.php?pid=1242(深搜或广搜)

2 : http://poj.org/problem?id=1011(剪枝)

3http://poj.org/problem?id=1077IDSA*IDA*,  BFS

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){

        Top = 0;

        Stack[top ++]  =start_state;  //初始状态入栈

        While(top){

                  Temp = stack[--top] ;  //取出栈顶元素

                  If( Is_goal(temp) ) return temp;  //如果找到了则返回

                For(i = 枚举由temp产生的状态){

                        If(i 合法) strack[top ++] =I;

                  }

   }

}

使用递归(即使用系统的栈)

Int dfs(now_state,MAX_deep){

   If(now_state.deep > MAX_deep) return ;

        If( Is_goal(temp) ) return temp;  //如果找到了则返回

        For(i = 枚举由temp产生的状态){

                If(i 合法) dfs(i,deep)

        }

}

 

手写栈在内存消耗和时间上都要优于递归,如果写的递归返回栈溢出的信息或者超时,那就改成手写栈试试。

 

由于深搜是不见棺材不掉眼泪的,因此很容易走到很深而且没有解的地方,而又可能在很浅的地方又有解,为了避免这种问题,引入了IDS,迭代加深搜索,就是控制每次搜索的深度进行深搜:

For(deep =初始深度; deep< 最大深度;deep ++)dfsstart_statedeep);

虽然deep每次增加使我们又将所有状态重新搜索了一遍,但是由于深搜的时间复杂度是深度的指数级,因此前面的状态用的时间对当前深度影响不会很大。

 

 

二:广度优先搜索

广搜在求解问题时时间效率很高,尤其是求解最优性问题。但是由于我们要将在未到达目标状态前的所有状态都存下来,因此空间消耗太大,而且大部分问题每一层的节点数都是以指数级方式增长,因此循环队列也解决不了内存问题。广搜是用队列来实现的,而且在广搜被用到求解最优问题时,队列一定要关于某一关键字递增

int bfs(){

        head = tail = 0;

        queue[tail ++] = start_state;

        while(head != tail){

                  now_state = queue[head ++];

    If( Is_goal(temp) )return temp;  //如果找到了则返回

                  For(i = 枚举由temp产生的状态){

                        If(i 合法) queue[tail ++] =I;

                }

}

}

 

 

 

例子.

题目1的题意是说,在一个n×M的网格里要从a走到r。‘.’是路,‘#’是敌人,走一步需要花费1,打一个敌人需要花费1,求从a走到r的最少花费。这个题目用深搜很好写,遇到’.’则花费加1,继续搜。遇到‘#’则花费加2,继续搜。但是这是最优化的问题,明显用广搜更加合适,而且如果这个题的数据加强到1000,深搜很有可能超时。对于之前遇到’#’不能走的问题,我们直接用一个队列,把由当前状态扩展出来的结点放到队列尾部即可,但是这用方法对于本题是不行的。为什么会出现这种情况呢,因为花费有两种,如果只是按时间顺序的话,先扩展的点不一定就是最小的花费,有可能从另一个地方到达他又更小的花费。而之前的问题里,花费只有一种,因此花费和时间是等价的。

想想上面提到的一句话“队列一定要关于某一关键字递增”此题的关键字就是花费,当我们把一个被扩展出来的节点放入队列是,一定要保证队列关于花费单调递增,也就是说我们每次拿出来的去扩展节点的点,必须是队列中花费最小的点。这样才能保证最优解的正确性。这里通常用单调队列或优先队列实现。

 

 

 

题目2是一道经典的剪枝的题目。剪枝这种东西灵活性很强,具体题目都不一样,大概经常用的有1:对原始数据做排序、打乱等预处理。2:当前解和最优解比较。3:分析剩下数据的特点。

 

 

        题目38数码问题,由于9位数字规模的大小,因此还能进行判重操作,所以能用的算法比较多。

1:判重。

由于棋盘转化成数字是9位,因此肯定要hash判重,否则复杂度太高。对于这种全排列问题。有一种很强的没有冲突的hash方式叫“康托展开”的东西。我们观察全排列,会发现对于每一个排列,都会有个独一无二的东西,那就是逆序数序列,例如:

数列  8 7 6 5 4 2 3 01

 逆序数0 1 2 3 4 5 5 7 7

然后还有一个东西叫“变进制数”

我们经常使用的数的进制为“常数进制”,即始终逢p1。例如,p进制数K可表示为

   K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 <=ai <= p-1),

P =10就是10进制.它可以表示任何一个自然数。

变进制数是每个位置i,逢pi进一。

有这样一中变进制的方法:第1位逢21,第2位逢31,……,第n位逢n+11

它的表示形式为  K = a1*1! + a2*2! + a3*3! + ...+ an*n! (其中0 <= ai <=i),
也可以扩展为如下形式(因为按定义a0始终为0),以与p进制表示相对应

K = a0*0! + a1*1! + a2*2! + a3*3! + ... +an*n! (其中0 <= ai <=i)。

可以证明这种变进制在进位方面是正确的。而这个方法下,n个数的全排列的最大值只有

N +1)!-1

因此。我们将棋牌的逆序数列和这个变进制结合起来,就可以用O1)的速度为棋牌判重。

关于变进制的详细内容:http://bbs.chinaunix.net/viewthread.php?tid=1283459

 

IDS】(迭代加深)

上边我们讲到了如何高效判重,下一步就是搜索了。对于棋牌这种问题,由于搜索方向的不同,很容易错过解而陷入很深的地方,因此我们使用IDS算法,迭代加深搜索,这样才可以。

 

【双向广搜】

双向广搜是为了减少广搜的内存,增加速度。从末状态和初状态同时出发进行扩展节点,并判断是否有交集,发生了交集就表示找到了解。每次不是机械的前面走一步,后面走一步,而是尽量找节点数少的那一方去拓展。判断交集用hash就行。

广搜的效果是一个三角形,而双向广搜的效果是三角形中的一个菱形。

 

A*】(优先队列广搜)

之前的算法都是没有去管当前状态有什么特点就去盲目的扩展,最多就是在转移的时候做一些优化的手脚。A*算法是启发式搜索,我们去分析当前状态,为状态打分。这样搜索就变的更加理性了。有三个参数:(x):当前状态x的估价函数,表示状态x到目标状态距离的一个下界。假设x到目标的最短距离是dp。则要求H(x) <= dp; G(x)从起始状态到当前状态的距离。F(x) = G(x ) + H(x) 状态x的启发函数,相当于一个打分。

当我们要扩展节点时,启发函数值越小的,相对来说对我们越有利,因此在广搜中加入启发函数,每次我们都给每个状态计算出启发函数值,然后重从列中找函数值最小的节点进行扩展。为了效率,这里要使优先队列。而且某些点的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 *算法实现其来都没有上面的算法麻烦,因为他什么数据结构之类的东西都不要,但是IDA *对启发函数的要求较高。

 

 

Int dfs(intdeep){//深度限制

        Top = 0;

        Stack[top ++]  =start_state;  //初始状态入栈

        While(top){

                  temp = stack[--top] ;  //取出栈顶元素

                  If( Is_goal(temp) ) return temp;  //如果找到了则返回

If(H(temp) + G(temp) > deep)continue; //如果超出深度限制,则不去扩展。

                For(i = 枚举由temp产生的状态){

                        If(i 合法) strack[top ++] =i;

                  }

   }

}

 

 

 

 

由于对搜索不熟,因此漏洞百出,欢迎批评指正。

由于长期聊QQ,所以错字较多,欢迎批评指正。