• 步步为营(十五)搜索(一)DFS 深度优先搜索


    前方大坑预警!

    先讲讲什么是搜索吧。

    有一天你去一个果园摘梨子,果农告诉你。有一棵树上有一个金子做的梨子,找到就是你的,你该怎么找?
    地图例如以下:
    S 0 0 0 0
    0 0 0 0 0
    0 0 0 0 0
    0 0 0 0 0
    0 0 0 0 G

    废话。挨个找呗~ 这就叫做搜索。

    在一个区间内找到符合条件的值的过程,就是搜索。(?)

    最简单的就是穷举,挨个找。

    由于都能够走,所以从(1,1)到(n。n)一路走过去,能找到即可。

    可是果农告诉你,这个果园有的位置是肥料坑~你不能过去(当然。你非要去坑里游两圈也不拦着你),那么这个果园的地图就成了这个样子。

    S 0 1 1 0
    0 1 0 0 0
    0 0 1 1 0
    0 1 0 0 0
    0 0 0 0 G

    这个时候,果园又来了好几个摘梨子的。果农就说:“金梨子仅仅有一个,你们谁最先找到就归谁!

    那么怎样去寻找到一条最短的路程呢?

    这时候就该用一些策略了,于是,DFS(深搜)登场了。

    首先。为什么叫深度优先搜索?

    深度,在这样的环境下,能够看做离起始位置的步数。

    更学术点的说法。能够看做“单位距离下。离起始状态的长度”

    假设你喝水,水杯里刚開始有一升水。你每次喝掉一百毫升。那么你喝掉两百毫升的状态和喝掉三百毫升的状态相比,喝掉三百毫升的这个状态“深度更大”。

    假设你在走地图,从(0,0)開始,一次走一步。

    那么你位于(2。3)的状态和你位于(4,5)的状态相比,(4,5)的这个状态“深度更大”

    明确什么叫深度。那么DFS,也就是深度优先搜索的概念就能够明确了。

    对于当前状态n。假设找到一个满足条件的下一个状态m,无论接下来还有没有符合条件的状态,都開始对m进行搜索。假设当前状态没有符合条件的下一个状态。就回溯到这个状态的上一个状态。

    也就是说。假设m没有符合条件的下一个状态点,就继续对n进行搜索。

    那么能够用伪代码表示为:

    //对于状态a进行推断
    int fun(status a)
    {
        if(a是结束条件)
        {
            找到解,进行操作
        }
        for(进行寻找)
        {
            if(找到了符合条件的下一个状态b)
            {
                //对B进行推断
                fun(b);
            }
        }
    
        找不到符合条件的状态。退出函数,返回到上一层。

    }

    那么对于果园的地图,我们的部分状态是这样的:
    S 0 1 1 0
    0 1 0 0 0
    0 0 1 1 0
    0 1 0 0 0
    0 0 0 0 G

    从S点開始,设S点坐标为(1,1)

    (1,1)
    ->(1,2)
    ->(1,1)(注意,这里回溯到了(1,1)点)
    ->(2,1)
    ->(3,1)
    ->(3,2)
    ->(3,1)(回溯到3,1点)
    ->(4,1)
    ->(5,1)
    ……
    ->(5,5)

    可是要注意,DFS由于递归调用。时间复杂度会非常高(平均时间(N^3*1/3)),所以要注意优化的技巧。

    最经常使用的就是剪枝。对于这道题目来说,对于訪问过的点不再进行二次訪问,减小递归层数,就是剪枝的一种表现。

    至于其它技巧…… 这些还是自己摸索的靠谱。

    对于每种找到结果的方案,记录步数,最后便可获得最小的步数。可是,假设要记录获取最小步数的整个过程。DFS就显得力不从心,由于递归调用的问题。DFS并不能保证自己得到的当前解就是最优的解。

    所以,DFS适合用来推断某个状态是否能达到,或者能够有多少种方案取得终于解。不适合用来做状态记录。

    而针对单个最优解和状态记录,更为合适的方法是BFS。

    代表题目:N皇后问题

    N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自己主动攻击)。

    #include<stdio.h>   
    #define N 15   
    
    int n; //皇后个数   
    int sum = 0; //可行解个数   
    int x[N]; //皇后放置的列数   
    
    
    int place(int k)   
    {   
        int i;   
        for(i=1;i<k;i++)   
          if(abs(k-i)==abs(x[k]-x[i]) || x[k] == x[i])   
            return 0;   
        return 1;   
    }   
    
    
    int queen(int t)   
    {   
        if(t>n && n>0) //当放置的皇后超过n时,可行解个数加1。此时n必须大于0   
          sum++;   
        else  
          for(int i=1;i<=n;i++)   
          {   
              x[t] = i; //标明第t个皇后放在第i列   
              if(place(t)) //假设能够放在某一位置,则继续放下一皇后   
                queen(t+1);    
          }   
        return sum;   
    }   
    
    int main()   
    {   
        int t;   
        scanf("%d",&n);   
        t = queen(1);   
        if(n == 0) //假设n=0。则可行解个数为0,这样的情况一定不要忽略   
          t = 0;   
        printf("%d",t);   
        return 0;   
    }  
  • 相关阅读:
    读取INI配置文件
    在VB编程中,若一行代码太长需要换行时,行尾要加什么符号
    使用order by和group by的分析
    转 Sqlserver_left join 、right join、 inner join 用法
    Python 字典(Dictionary)操作详解
    转sql server新增、修改字段语句(整理)
    Winform TextBox中只能输入数字的几种常用方法(C#)
    数据库的范式,第一、二、三、四、五范式、BC范式
    【操作系统】银行家算法
    转 图解排序算法(三)之堆排序
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7052764.html
Copyright © 2020-2023  润新知