• 搜索理论


    宽度优先搜索bfs 

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

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

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

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

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

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

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

    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)对深度较浅的层数会重复搜索,并不推荐使用

    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)
      保存路径,即 从终点开始,每个节点沿着父节点移动直至起点,这就是你的路径

  • 相关阅读:
    20165204 缓冲区溢出漏洞实验
    2019-2020 1 20175230 实验四 外设驱动程序设计
    2019-2020-1 20175230 实验三 并发程序
    2019-2020-1 20175208 20175218 20175230 实验二 固件程序设计
    2019-2020-1 20175208 20175218 20175230 实验一 开发环境的熟悉
    2018-2019-2 20175230 实验五《网络编程与安全》实验报告
    2018-2019-2 20175230实验四 《Android开发基础》实验报告
    2018-2019 2 20175230《Java程序设计》第十一周学习总结
    2018-2019-2 20175230 实验三《Java面向对象程序设计》实验报告
    MyCP
  • 原文地址:https://www.cnblogs.com/hpustudent/p/2155233.html
Copyright © 2020-2023  润新知