zoukankan      html  css  js  c++  java
  • 搜索理论

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

  • 相关阅读:
    LeetCode 83. Remove Duplicates from Sorted List (从有序链表中去除重复项)
    LeetCode 21. Merge Two Sorted Lists (合并两个有序链表)
    LeetCode 720. Longest Word in Dictionary (字典里最长的单词)
    LeetCode 690. Employee Importance (职员的重要值)
    LeetCode 645. Set Mismatch (集合不匹配)
    LeetCode 500. Keyboard Row (键盘行)
    LeetCode 463. Island Perimeter (岛的周长)
    115.Distinct Subsequences
    55.Jump Game
    124.Binary Tree Maximum Path Sum
  • 原文地址:https://www.cnblogs.com/hpustudent/p/2155233.html
Copyright © 2011-2022 走看看