【笔记】搜索专题总结

搜索专题总结

目录

一、 搜索算法
a) 深度优先搜索(dfs)
b) 广度优先搜索(bfs)
c) 迭代加深搜索(ID)
d) 启发式搜索(A*)
二、 剪枝与优化
a)深度优先搜索剪枝
    1、最优性剪枝
    2、可行性剪枝
    3、搜索顺序
    4、记忆化搜索
b)广度优先搜索剪枝
    1、双向广搜
    2、判重方法
1)hash表
        2)STL大法
c)迭代加深搜索优化
    IDA*
d)其他类型剪枝与优化
    1、状态压缩
        1)二进制优化
            A空间优化
            B时间优化
        2)其他进制优化
    2、数据处理
        1)离线数据
        2)预处理数据

一、 搜索算法

a)深度优先搜索(dfs)

(1) 思想:(试探法)从一条路往前走,能进则进,不能进则退回来,换一条路再试 像走迷宫一样

(2) 实现方法:递归,用栈保存

(3) 优缺点:

①优点:程序简单,容易上手,空间小,容易调试
②缺点:呆滞,求最优路时速度慢

(4) 经典例题:

    ①素数环:把1到20排成一个环,使得环中每俩相邻数字的和是个质数
    ②八皇后:八个皇后放在一个8×8的棋盘上,不可互相攻击

(5)模板:

Pseudo code:
    void dfs(int x){
        if(x>tie){      已达到目标状态
            print();
            return ;   停止
        }
        for(int i=1;i=n;i++)
            if(bo[i]){
                bo[i]=0;  标记
                dfs(x+1);  深搜
                bo[i]=1;   取消标记
            }
        return ;          回溯
    }

b)广度优先搜索(bfs)

(1) 思想:(走一步再走一步)层层扩展,先将当前所有点的下一层扩展,再将扩展后的点再次扩展……像传染病传染一样

(2) 实现方法:像削金字塔一样一层一层地遍历,用队列保存

(3) 优缺点:

①优点:时间小,找最优路时比深搜不知道好到哪里去了;
②缺点:空间巨大,编程较为复杂,调试很麻烦

(4) 经典例题:八数码水题:给出3×3的方格,留出一个空格,给出起始状态与目标状态,求最小操作数量

(5) 模板:

Pseudo code:
    int bfs(){
        初始化……
        queue <node> q
        q.push(a);
        while(!q.empty()){
            node now=q.front();
            q.pop();
            for(…){
                node next=now;
                if( next 符合目标状态)return next.walawala;
                对next进行扩展;
                q.push(next);
            }
        }
        return -1;
    }
    int main(){
        cin>>…;
        cout<<bfs();
        return 0;
    }

c)迭代加深搜索(ID)

(1) 思想:(折返跑)深搜,

(o)o[BINGO!] ( o ゜ ▽ ゜ ) o ☆ [ B I N G O ! ]
每次深搜层数最多不超过constraint,
若没搜到答案,则减小限制,扩大搜索层数上线

(2) *实现方法:*dfs+一个if

(3) 优缺点:①优点:结合深搜与广搜的特点,空间小,时间小(ˇˍˇ) ,编程简单,简明易懂,是您居家旅行,暴力骗分的必备技能
②缺点:实在找不到缺点,太完美了,(⊙o⊙)哦,有一个很勉强的,就是加深后,前面的东东重复搜索了

(4) 经典例题:埃及分数:将一个分数分解为+++…+;并使n尽可能小,当解有多个时,取sn最小时的解

(5) 模板:

Pseudo code:
    int constraint,deep=…;
    bool dfs(int x){
        if(x>constraint)return true;
        if(有解){bo=1;return false;}
        +dfs模板
    }
    int main(){
        cin>>…;
        while(dfs())constraint;
        cout<<answer;
        return 0;
    }

d)启发式搜索(A*)

(1) 思想:(贪心)每次取当前最优点扩展;

(2) 实现方法:每次取当前扩展但未处理的点中预估值最高的,进行处理并扩展该点的连接点,将扩展的点放入优先队列,再从优先队列中取值;

(3)优缺点:①优点:搜索平均时间快,考场暴力骗分利器;
②缺点:搜索不稳定,容易被卡,且最优解不稳定;

(4) 实现过程:

1、把起点加入到堆中
2、重复以下步骤
  a、从堆中找出F最小的节点,并把它当做当前的操作节点
  b、检查当前点周围的点,如果已经在堆中看是否能通过当前点得到更小的G,如果能就更新那个点的G,F的值,如果在set中或者是障碍物(不可达)则忽略他们
  c、把当前点从堆中移除 ,加入set中
  d、当目标点加入set中时停止
3、保存路径,从目标点出发,按照父节点指针遍历,直到找到起点。
        (5) 经典例题:八数码难题:题目见上;

(5)模板:

Pseudo code:
    int get_h(int qq,int ee){
        return ……;
    }
    struct node{
        int a,b;
        int walawala;
        int h=get_h(a,b);
        bool operator < (const int &x)const{   以估价函数为关键字进行排序,大根堆
            return x.h>h;
        }
    }
    int bfs(){
        初始化……
        priority_queue <node> q
        set <node> c;
        q.push(a);
        while(!q.empty()){
            node now=q.front();
            q.pop();
            for(…){
                node next=now;
                if( next 符合目标状态)return next.walawala;
                对next进行扩展;
                if(!set.count(next)){
                    q.push(next);
                    c.insert(next);
                }
            }
        }
        return -1;
    }
    int main(){
        cin>>…;
        cout<<bfs();
        return 0;
    }

(7) 估价函数: A*的灵魂所在,估价函数写得好,分数翻番,写错了就扑街了

F(n)=g(n)+h(n)

g(n):从起点到n的距离

h(n):从n到终点的估价函数

常用估价函数:

①曼哈顿距离:h=|t.x-now.x|+|t.y-now.y|
                        网格中最常用的估价函数,保证不会剪到手,但效果较弱
②切比雪夫距离:h=max(|t.x-now.x|,|t.y-now.y|)
                        在国际象棋中国王式走法常用

其他类型的估价函数需根据题意自己设计,估价函数应朝着与现实数据接近的方向设计,当然,在一些特殊情况下,可另行设计(例如打暴力时若数据过大,则可使用强势的估价函数,剪去大部分枝强势骗分;反正就算估价正确也TLE

二、 剪枝与优化

a) 深度优先搜索剪枝(dfs)

1、 最优性剪枝:

(1) 思想:当目前状况已超过搜到的最优值,则返回

(2) 升级版:当目前状况+当前与目标的最小差值已超过最优值,则返回

(3) 适用范围:求最优值,且无负权值

(4) 注意事项:估价函数只可偏小不可偏大,且要求函数效率高

(5) 特点:结果绝对正确,剪枝效果适中,副作用较小

2、可行性剪枝:

(1) 思想:当目前状况已不满足于题意,则返回

(2) 升级版:每次仅判断当前拓展状况可能影响到的条件

(3) 适用范围:有限定条件的题

(4) 特点:结果绝对正确,剪枝效果适中,若不用升级版,则副作用可能极大

(5)*经典例题:*Betsy的旅行:给出n×n的方格,求(1,1)到(n,n)的全遍历路线数
利用思想(1):对于一条合法的路径,除出发点和目标格子外,每一个中间格子都必然有“一进一出”的过程。必须保证每个尚未经过的格子都与至少两个尚未经过的格子相邻(除非当时Betsy就在它旁边)。
但是:假如在每次剪枝判断时,都简单的对N2个格子进行一遍扫描,其效率的低下可想而知。因此,我们必须尽可能的简化判断的过程。
所以利用升级版:由于Betsy的每一次移动,只会影响到附近的格子,所以每次判断时,应当只对其附近的格子进行检查:即只通过对Betsy附近的格子进行判断,就确定是否应当剪枝

3、 搜索顺序:

(1) 思想:利用题目特征,判断搜索优先顺序,把可能得到解得分枝放在前边搜

(2) 升级版:其实就是A*

(3) 适用范围:只要你能分析好题目特征,都可以用

(4) 注意事项:千万别用这个求最优解(除非你在暴力骗分)

(5) 特点:

    ①若求可行解:超厉害的说(虽然没A*神),只要分析到位了,时间超短,空间也小,剪枝效果不稳定,平均情况较好,副作用小
    ②若求最优解:不是奔着暴力分去尽量别用,在搜完前无法证明得到的解是最优解

4、 记忆化搜索:

(1) 思想:在搜索时记录当前节点以后的搜索最优值,当搜索时发现当前节点已经被记录时(若当前状态不比节点储存值更优)直接调用,节约时间

(2) 升级版:当记忆化达到一定程度时,或搜索十分有规律,可进化为“动态规划”

(3) 适用范围:不同搜索路径不会改变已储存值的准确性(例如:每个点仅能走一次,并且不是树)或对一个点调用多次(可及时更新,一定程度上还是节约了时间的)

(4) 注意事项:一定要保证节点数据恒为正确或数据更新及时

(5) 特点:当对一棵树多次搜索或对一张稠密图进行搜索时效果明显,几乎无副作用;当节点数据不能恒为正确时需多次更新,效果较弱;若图退化成链或树且仅搜索一次,则无优化效果(并有那么一丁儿点的小小的常数时间)

(6) 经典例题:滑雪:给一张图上所有点数值,求图中所含的最长下降路径
分析:若采用暴力算法,对每一个点进行dfs,取maxx
但由于对于每一个点,以其为起点的最长下降路径可以确定,可以保证节点保存的数据恒为正确,若用记忆化搜索,每个点最多搜索一次,时间降为O(n)

b) 广优先搜索剪枝(bfs)

1、双向广搜:

(1) 思想:已知起始状态与目标状态,则从起始状态与目标状态分别广搜,每次分别仅扩展一层,并与对面的新扩展层进行比较,若有相同值则输出双方权值和

(2) 优缺点:

    ①优点(与bfs相比):速度较快,空间较小
    ②缺点:编码较为复杂,判重较为复杂

(3) 适用范围:已知起始状态与目标状态,求从起始状态到目标状态的最小值

(4) 注意事项:每次bfs仅扩展一层

(5) 模板:

Pseudo code:
    int bfs(){
        初始化……
        queue <node> q1,q2;
        q1.push(a);q2.push(b);
        int step1=0,step2=0;
        while(!q.empty()&&!q.empty()){
            扩展一层q1,判断是否与q2最外层有相同值 若有 return step1+step2;
            step1++;
            扩展一层q2,判断是否与q1最外层有相同值 若有 return step1+step2;
            step2++;
        }
        return -1;
    }
    int main(){
        cin>>…;
        cout<<bfs();
        return 0;
    }

2、判重方法:

(1)Hash表

1 思想: 以数组为储存空间,对状态进行高效判断是否存在

2 实现方法:数组+hash函数

3 优缺点:查找时间相比于STL较优,但空间没有STL那样有弹性

4 几种常用数字hash函数:

A取余法:
    a思想:对一个大数mod p(p为质数),作为地址
    b优缺点:优点:代码极其简单,p可以自己设置,空间与准确度之间可自己取舍
            缺点:数字大起来准确度不高


B随机化法:
    a思想:对于一个数t,首先用取余法求出t的hash函数中的某些值,再利用这些值进行hash运算
    b优缺点:优点:不会被坑爹数据坑到,肯定没人能故意卡它
            缺点:但也许会自己挂掉

C平方切割法:
    a思想:对一个数t进行平方,再取中间几位作为地址
    优缺点:优点:准确度高
           缺点:要求数字不得太高

D康拓展开:
    a思想:当一些数进行全排列时,进行数字分析,减小空间开销
    b适用范围:仅全排列时可用
    c公式:X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!
        其中a[i]表示:对于该全排列的第i位上的数字,在它的右边的位数上有a[i]个数字比它小。
    d优缺点:优点:100%避免冲突
             缺点:时间复杂度高:O(n^2)//还不如用set

5、关于字符串hash函数:

A转换法
    a公式:hash[i]=(hash[i-1]*p+idx(s[i]))%mod
    b优缺点:优点:貌似找不出
            缺点:有点随机,冲突可能性较高,空间无弹性
B查找树(其实不是hash)
    a思想:将字符串建树,进行搜索(单词查找树)
    b优缺点:优点:时间优,空间小
            缺点:程序复杂
C STL大法好

(2) STL大法

1 map
    a用法:存储:将存在过的值进行map映射到一个特殊值上
          查询:时若映射值等于特殊值则存在过
    b优缺点:
    优点:可以设特殊值以直接找到存在的位置,代码简单且啥数据类型都可以
    缺点:速度贼慢
2 set
    a用法:存储:set<…>c;c.insert(要存储的东东);
         查询:c.count(要查的东东)若为1则存在
    b优缺点:优点:代码简单,速度较快,数据随意,空间有弹性
            缺点:速度较hash较慢

c) 迭代加深搜索优化(IDA*)

(1) 思想: ID时用A*

(2) 适用范围: 能用ID就能用IDA*

(3) 优缺点:

    优点:空间小,时间小,用了都说好
    缺点:代码稍复杂

(4) 模板:

Pseudo code:

int get_h(int qq,int ee){
    return ……;
}
struct node{
    int a,b;
    int walawala;
    int h=get_h(a,b);
    bool operator < (const int &x)const{   以估价函数为关键字进行排序,大根堆
        return x.h>h;
    }
}
int constraint,deep=…;
int dfs(int x){
    初始化……
    priority_queue <node> q
    set <node> c;
    q.push(a);
    while(!q.empty()){
        node now=q.front();
        if(now.walawala>constraint)return -1;
        q.pop();
        for(…){
            node next=now;
            if( next 符合目标状态)return next.walawala;
            对next进行扩展;
            if(!set.count(next)){
                q.push(next);
                c.insert(next);
            }
        }
    }
    return -1;
}
int main(){
    cin>>…;
    while(ans==-1)ans=dfs(),constraint+=deep;
    cout<<answer;
    return 0;
}

(5) 经典例题:十五数码:题目与八数码类似,不过是4×4的方格

d) 其他类型剪枝与优化

1、 状态压缩

1) 二进制优化

A空间优化:

1 思想:利用计算机二进制保存的特点,用一个整形变量保存数十个bool变量,整形变量二进制的第i位上为1即bo[i]=12 优缺点:优点:空间满载时可优化成原来的1/8,运算速度快
缺点:代码难以构思,状态单一,难以编译

3 适用范围:当保存量很大且保存类型为bool时才可用

4 例题:胜利大逃亡(续):有k1把钥匙,k2扇门,求最短路
分析:若用bool数组保存钥匙状态,则空间开销较大,且结构体转移时速度较慢;但一旦用了二进制优化,现在头不昏眼不花,空间小了,也顺带少了一点常数时间


B时间优化:

1 思想:利用位运算那强大的常数时间,节约时间

2 优缺点:优点:运用到极致则有奇效(例题中会说明)
 缺点:一般想不到二进制时间优化

3 适用范围:想到并证明正确即可使用

4 经典例题:n皇后问题:参照八皇后,#define ‘八’‘n’
分析:若采用传统dfs方法进行广搜,即便利用可行性剪枝升级版也会在n较大时扑街,所以可以考虑用二进制保存每一行能否放置当前皇后,设置l,ld,rd分别保存该列,左斜线,右斜线上是否被攻击,并在dfs下一层时ld<<1,rd>>1,这样就以迅雷不及掩耳之势找到了当前行能放置皇后的点,并免去了状态转移的n次运算或是可行性剪枝时较大的副作用

2) 其他进制优化

1 思想:利用n进制的储存,减小空间开销,用时间换空间

2 优缺点:
    优点:空间开销减小程度大大的说,因为不用进制优化,至少也要用short,腻大了
    缺点:速度没了二进制的支持,甚至比O(1)还慢

3 例题:在一张地图上,每个点最多经过俩次
分析:只要时间够,空间爆了,便可用三进制用时间换空间

2、 数据处理

1)离线数据

1 思想:利用考试喝茶的时间,放一个程序打出与输入无关但要用的万能数据以节约时间跑暴力

2 升级版:人送外号 ~(~ ̄▽ ̄)~ 打表 ~( ̄▽ ̄~)~

3 适用范围:预处理的数据必须与输入无关

4 注意事项:
    A要先算好程序运行时间,以防快考完了还没跑完 
    B要算好数据量,以防源程序过大系统直接pass//亲身经历
                    ┑( ̄Д  ̄)┍

5 特点:百分百节约时间

6 经典例题:亚瑟王的宫殿:多个骑士与一个国王在n×m方格上,要求最终所有人到达同一点,(国王骑士走法参照国际象棋)骑士可以接国王,俩人在一起时步数算一人,求最小总步数

解析:可以离线处理好从(1,1)到(maxn,maxm)的骑士步数,方便以后调用

2) 预处理数据

1 思想:防止一个测试点中多组输入数据处理时重复计算

2 适用范围:一个共有数据,多组分数据

3 注意事项:要先算好程序运行时间,以防预处理占用大多数时间,并且当组数少并且预处理
繁杂时不宜使用

4 特点:当数据组数较多时很节约时间,但当组数少并且预处理繁杂时很坑

5 经典例题:亚瑟王的宫殿:见上

解析:可以预先处理好从(1,1)到(i,j)的骑士步数,方便以后调用(这样较离线处理也许可以保证源程序的空间不爆)
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值