搜索理论

  宽度优先搜索bfs 

一、搜索的bfs,宽度优先搜索,一般用于求最短的得到到目的地的距离,有个起始点,先把这个起始点入队列,不要忘记将这个起始点标记为已经利用,不然会走回来的,然后是与这个起始点的周围的点的监测,若可以行的通,我们一一检测这些扩展出来的点,若是目的地就结束了,若不是目的地,将改点标记为已经利用,并将该点入队列。

 二、搜索的双向bfs,双向的bfs一般来说可以用单向解决,但是单向的效率上还是差了点,适合双向bfs的题目有一些共同特点:给出起始和最终状态,让求出一条从初始到末状态的最短路。

双向bfs的实现过程: 从初始结点开始扩展,每扩展一层,在从目标节点按照产生系统相反的办法来扩展结点,直到从两端扩展出的某个结点相遇,那么中间的这条路径一定就是从初始到终点最短的路径。

注意: 双向广搜必须按层扩展,才能保证结果的正确性

无解状态就和普通bfs无异, 所以判断无解函数不可省略.  

     路径的保存:有时候除了要找到一个目标状态,还需要保存从初始结点到它的路径。路径可以随状态保存,也可以用链表穿起来。如果保存的所有结点或者路径长度不定,推荐使用链表。

    判重:除非隐式图是无环的,或者有很强的结点扩展方式来避免生产重复,广度优先搜索是需要判重的。但要注意判重函数的费用!!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int  DBFS()
{
      
     Queue Q[2];
     Map M[2];
     int  now=0;
     Q[0].push(start);
     Q[1].push(end);
     M[0][start]=M[1][end]= true ;
     While( !Q[0].empty()   ||  !Q[1].empty)
     {
         int  nowg=now&1,count=Q[nowg].size();
         while (count--)
         {
             curstate=Q[nowg].front();
             Q[nowg].pop();
             if ( M[nowg^1].find(curstate) != M.end() ) return  now;
             for  every extstate=extend(curstate)
                 if (M[nowg].find(extstate) == M.end())
                 {
                     Q[nowg].push(extstate);
                     M[nowg][extstate] = true ;
                 }
         }
         ++now;
     }
     return  -1;
}

 

 深度优先搜索dfs

一、深度优先搜索一般是给定搜索的深度,或者没有深度有结束条件的,可以用结束条件代替深度,都可以用dfs

二、dfs回溯用来解决搜索所有解的问题,但是纯粹的dfs回溯往往会超时,一般与剪枝联系在一起

三、剪枝一般分为可行性剪枝和最优化剪枝 

(1)可行性剪枝, 剪去不能到达终点的路径

(2)最优化剪枝, 剪去不能到达终点和不可能是第一个到达终点的路径

 迭代加深搜索

一、用于不知道最短转移次数,为了弥补广搜的空间不足的问题而产生的搜索方法

形式为:for( depth=init(); dfs() ; depth++ ) 

使用是要注意:

(1)由于不知道上界, 如果问题无解的情况下就不能用这个了

 (2)对深度较浅的层数会重复搜索,并不推荐使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Procedure ID-dfs(dep:integer);
Var
   J: integer;
Begin
   If dep>深度的限界 then  exit // 如果搜索的深度大于限界,则返回上一层
   For j:=1 to n  do   // 按照规则生成子结点
    If 子结点安全 then 
     Begin
       入栈;
       If 子结点是目标结点 then 对目标结点进行处理,退出程序
           Else id-dfs(dep+1);
       退栈;
     End;
End;
       
For i:=1 to depmax  do  // 枚举深度的限界
Begin
     Id-dfs(i);
     If 运行超时 then  break ;
End;

从上述迭代加深搜索算法的实现过程和框架,我们可以看出,迭代加深搜索算法就是仿广度优先搜索的深度优先搜索。

既能满足深度优先搜索的线性存储要求,又能保证发现一个最小深度的目标结点。


A*算法

A*类似贪心的BFS 

BFS一般扩展最小消耗的点,A*算法在另一方面 扩展最有希望的点(估价函数返回值最优)

状态被保存在一个优先级队列中,按照cost*价值排列。对一个可容许的估价函数,第一个找到的状态保证是最优的。

一、A*算法属于启发式搜索,扩展结点的顺序是基于广度优先搜索,但是不同之处在于每扩展一个子结点需要计算估价函数f(s),以估算起始结点,经过该结点到达目标结点的最佳路径代价,每当扩展结点时,总是希望在所有待扩展结点中选择具有最小

f(s)值的结点作为扩展对象,以便使搜索尽量沿着最有希望的方向进行。 

假设s表示一个状态,

f(s):启发式函数,表示从初始状态开始,经过状态s的路径上最少需要多少代价才可以找到终点之一。

g(s):从初始状态到当前状态s的最小代价

h(s):估价函数,从当前状态s到达终点之一最少需要多少代价

有关系式f(s)=g(s)+h(s)

一种具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法的充分条件是:

A*必须满足两点:

h(s)<=h*(s) 和 f(s)必须是递增的

1) 搜索树上存在着从起始点到终了点的最优路径。 

2) 问题域是有限的。 

3)所有结点的子结点的搜索代价值>0。 

4)h(n)=<h*(n) (h*(n)为实际问题的代价值)。 

当此四个条件都满足时,一个具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法,并一定能找到最优解。

对于一个搜索问题,显然,条件1,2,3都是很容易满足的,而 

条件4): h(n)<=h*(n)是需要精心设计的,由于h*(n)显然是无法知道的。

所以,一个满足条件4)的启发策略h(n)就来的难能可贵了。不过,对于图的最优路径搜索和八数码问题,有些相关策略h(n)不仅很好理解,而且已经在理论上证明是满足条件4)的,从而为这个算法的推广起到了决定性的作用。不过h(n)距离h*(n)的程度不能过大,否则h(n)就没有过强的区分能力,算法效率并不会很高。对一个好的h(n)的评价是:h(n)在h*(n)的下界之下,并且尽量接近h*(n).


当然,估值函数的设计也就就仅仅是f(n)=g(n)+h(n)一种,另外的估值函数“变种”如:f(n)=w*g(n)+(1-w)*h(n) ,f(n)=g(n)+h(n)+h(n-1)针对不同的具体问题亦会有不同的效果。

        A*算法最为核心的过程,就在每次选择下一个当前搜索点时,是从所有已探知的但未搜索过点中(可能是不同层,亦可不在同一条支路上),选取f值最小的结点进行展开。而所有“已探知的但未搜索过点”可以通过一个按f值升序的队列(即优先队列)进行排列。这样,在整体的搜索过程中,只要按照类似广度优先的算法框架,从优先队列中弹出队首元素(f值),对其可能子结点计算g、h和f值,直到优先队列为空(无解)或找到终止点为止。 

A*算法与广度优先和深度优先的联系就在于,当g(n)=0时,该算法类似于DFS,当h(n)=0时,该算法类似于BFS , 这一点,可以通过上面的A*搜索树的具体过程中将h(n)设为0或将g(n)设为0而得到。 

       路径最优问题,简单来说,就是在两个结点之间找一条最短路径。有的朋友不禁要问,这个问题不是已经有Dijkstra算法可以解决了吗?此话不假,但是不要忘了Dijkstra算法的复杂度是O(n^2),一旦结点很多并且需要实时计算的话,Dijkstra就无法满足要求了。而A*来处理这类有需要实时要求的问题则显得游刃有余。

       在路径最优问题中,用来作为启发函数关键部分的h(n)其实很容易选,那便是当前结点至最终结点的距离,这个距离既可以是曼哈顿距离(|x1-x2|+|y1-y2|),亦可以是Euclid(欧几里得或者欧式距离) 距离(直线距离)。都可以在较快的速度下达到问题的最优解。 

伪代码

主要搜索过程伪代码如下: 
  创建两个表,OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。  
  算起点的估价值; 
  将起点放入OPEN表; 
  while(OPEN!=NULL)  
  {  
  从OPEN表中取估价值f最小的节点n;

        if(n节点==目标节点){  
     break;  
  }  
  for(当前节点n 的每个子节点X)  
  { 
  算X的估价值;  
  if(X in OPEN) 
  {  
  if( X的估价值小于OPEN表的估价值 ){  
  把n设置为X的父亲;  
  更新OPEN表中的估价值; //取最小路径的估价值  
  } 
  } 
  if(X inCLOSE) {  
  if( X的估价值小于CLOSE表的估价值 ){  
  把n设置为X的父亲;  
  更新CLOSE表中的估价值;  
  把X节点放入OPEN //取最小路径的估价值  
  }  
  } 
  if(X not inboth){  
  把n设置为X的父亲; 
  求X的估价值;  
  并将X插入OPEN表中; //还没有排序  
  } 
  }//end for 
  将n节点插入CLOSE表中;  
  按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。 
  }//end while(OPEN!=NULL) 
  保存路径,即 从终点开始,每个节点沿着父节点移动直至起点,这就是你的路径 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值