搜索专题总结

来源:互联网 发布:开农村淘宝怎么赚佣金 编辑:程序博客网 时间:2024/05/16 15:22

搜索专题总结

目录

一、 搜索算法

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!]
每次深搜层数最多不超过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)的骑士步数,方便以后调用(这样较离线处理也许可以保证源程序的空间不爆)
原创粉丝点击