• 搜索技巧


    概述

    NOIP的搜索主要考察代码能力,思维难度不高。常用搜索的方法有DFS,BFS和迭代加深。常见优化有剪枝、状态压缩、双向搜索和启发式搜索

    基本概念

    DFS

    适合状态存储不了的情况,相对符合人类的思考习惯(我们只需要考虑当前的子问题)
    代码难度相对BFS较为简单

    BFS

    适合状态容易存储的问题(难以处理序列操作等,需要压进结构体),适合搜索深度可能会很深,甚至不知道有多深的情况

    ID迭代加深

    确定深度上限并进行DFS,可以方便回溯且防止一条路走到黑。常用来解决确定步数的题目,可以把最优化问题转化为可行性问题。

    优化

    剪枝

    剪枝可以极大地降低搜索的效率,同时也使其复杂度难以衡量(常骗分)。
    剪枝方法的特殊性比较强,必须因题而异,一般地说,剪枝可以分为两类:

    1. 可行性剪枝:某个状态一定到达不了目标状态
    2. 最优化剪枝:某个状态的所有到达目标状态的方案一定不是最优值

    出现这两种情况就说明这个状态及后续状态都不用再访问

    例1:P1731 [NOI1999]生日蛋糕

    首先考虑可行性
    一个比较简单的,当前体积>规定体积则退出
    思考剪枝的一个重要思路是在极端情况下能不能满足题目要求
    那么对于某个状态,如果剩下的层数全部让体积最大,都不能达到规定的体积,那么这个状态就可以cut掉
    其次考虑可行性剪枝
    研究题目性质可以发现,俯视图的面积在最开始的时候就确定了
    所以我们每层搜索只需要考虑侧边的高度
    这里同样可以考虑极端情况
    在当前层直接把所有体积用完(不考虑后面的层),如果这样表面积仍然大于之前最优方案的值就cut
    因为半径越大,单位体积需要的高度越小,而半径是随高度递增的

    双向搜索

    双向BFS,就是在起点和终点都很清楚的情况下,把起点和终点同时入队,或者进两个队,共同进行bfs,当二者第一次相遇时为最优解
    这样做的目的是减轻bfs状态访问太多的症状
    其实DFS也能双向,但是不好写
    众所周知,通常情况下,搜索树越往下分叉越大,子节点可能呈指数级暴增,而上层状态相对较少
    所以让两头共同搜索不仅仅是让时间减半,搜索树高度减半的结果可能是显著的

    A*

    盲目搜索与启发式搜索

    前面提到的所有搜索都是都叫做盲目搜索,都是在遍历了所有状态后才能够判断是否可行。
    在搜索时,有些状态成为正解的概率明显大于其他状态。启发式搜索就是用启发信息引导搜索。

    启发信息

    不同的启发信息有不同的强度。
    当启发信息过强时,不能保证最优解。
    当启发信息过弱时,优化效果不明显。
    我们引入启发信息,显然既想要得到最优解,又想让程序跑得快。
    所以就要合理设置启发信息。

    A算法

    定义评价函数,对当前的搜索状态进行评估,找出有希望的节点扩展,这就叫做A算法(启发式搜索算法A)

    通常,A算法的评价函数形如(f(n)=g(n)+h(n)),其中(f(n))是评价函数,(g(x))(n)已经用掉的开销,(h(n))是一个启发式函数

    我们还需要定义一些符号:

    s、t:分别表示搜索的起点和目标
    g(n):从s到n的最小代价
    h
    (n):从n到t的最小代价
    f(n)=g(n)+h*(n):从s经过n到t的最小代价

    实际上g(n)、h(n)、f(n)分别是g(n)、h(n)、f*(n)的估计值

    A算法还包含两个数据结构,叫做closed表和opend表
    closed表里储存的是已经访问过的节点
    opend表里储存的是当前能访问到,但是还没有访问的节点
    其实就是一个堆+bool数组的事情,但是这种说法还是不少,可能在其他领域有作用

    A算法具体流程

    首先把出发点加入opend表里
    然后进行bfs循环,每次循环找出opend表中f函数最小的状态,然后遍历此节点能够访问的所有状态
    若被被遍历到的状态在closed表里根据A算法的特性,它们不必再被访问,若被遍历到的状态在opend表里,也可以无视他们,若遍历到的状态不在任何表里,则放入opend表中
    直至访问到目标状态即可结束循环

    A*算法

    A*算法为保证(h(x)<=h*(x))的A算法。具体体现在A算法不一定能够得到最优解,而A*算法一定能够得到最优解。(生活中主要使用A算法,使启发信息很强来保证效率和一定的质量)

    例题
    八数码问题

    若要用A*算法解决问题,最关键的就是设计h函数
    在保证h(n)<=h*(n)(最优解)的情况下让h(n)函数尽量大(跑得块)

    h1

    令h(n)=在n状态下,不在自己应该在的位置上的数字数
    这样h(n)显然不大于h*(n),因为每次交换位置的是空格和数字,不可能把两个数字同时归位,要让所有数字归位,移动步数至少为不在位的数字数

    h2

    我们可以发现,对于一个不在位置上的数,不仅至少要走一格让它回去,而且是至少要走当前位置到应在位置的曼哈顿距离
    因为一次只能移动一格,而且一次不能同时移动两个数
    所以我们可以把h(n)改成在状态n下,所有不在位的数字到目标位置的曼哈顿距离之和
    注意空格不在统计的范围内

    这样我们就设计了h函数,先不用管它是否是尽可能地大,但是至少比h(n)=0强,我们的程序已经得到优化了

    IDA*

    IDA*中的ID就是迭代加深的ID,A*还是那个A*

    IDA*的核心依然是一个评价函数f(n)=g(n)+h(n)

    不过这次利用它的方法为如果当前状态的评价函数f(n)大于迭代加深规定的搜索深度,就立刻回溯,不再深入
    通俗地说就是我们通过设计,发现某些状态n到终点至少要走h(n)步,而实际要走的不会比h(n)少
    如果g(n)+h(n)超过规定深度,那么无论如何状态n都不会搜到目标状态,可果断剪枝

  • 相关阅读:
    java IO流之详细总结
    位运算了解与复习多线程
    java 常见面试题总结(一)
    复习集合框架
    【面试题】java中高以上必会技能
    python-项目流程分析及优化查询方法
    python-day97--django-ModelForm
    python-day97--git协同开发
    python-day96--git版本控制
    python-day91--同源策略与Jsonp
  • 原文地址:https://www.cnblogs.com/guoshaoyang/p/11277815.html
Copyright © 2020-2023  润新知