搜索是编程的基础,是必须掌握的技能。——王主任

搜索分为盲目搜索和启发搜索

下面列举OI常用的盲目搜索:

  1. dijkstra
  2. SPFA
  3. bfs
  4. dfs
  5. 双向bfs
  6. 迭代加深搜索(IDFS)

下面列举OI常用的启发搜索:

  1. 最佳优先搜索(A)
  2. A*
  3. IDA*

那么什么是盲目,什么是启发?

  1. 举个例子,假如你在学校操场,老师叫你去国旗那集合,你会怎么走?
  2. 假设你是瞎子,你看不到周围,那如果你运气差,那你可能需要把整个操场走完才能找到国旗。这便是盲目式搜索,即使知道目标地点,你可能也要走完整个地图。
  3. 假设你眼睛没问题,你看得到国旗,那我们只需要向着国旗的方向走就行了,我们不会傻到往国旗相反反向走,那没有意义。
  4. 这种有目的的走法,便被称为启发式的。

左图为bfs,右图为A

提供一个搜索可视化的链接https://qiao.github.io/PathFinding.js/visual/

搜索算法浅谈

DFS

    基础中的基础,几乎所有题都可以出一档指数级复杂度暴力分给DFS,同时他的实现也是目录中提到的所有搜索算法中最简单的

dfs的核心思想是:不撞南墙不回头    孙学凤:物理人不撞南墙

举个例子:



你现在在一号点,你想找到树中与一号点连通的每一个点

那么我们考虑按照深度优先的顺序去遍历这棵树,即,假设你当前在点x,如果和x连边的点中有一个点y,满足y比x深,即y是x的儿子,并且y还没有被访问过,那么我们就走到y,如果有多个y满足条件,我们走到其中任意一个

如果没有y满足条件,我们返回x的父亲

按照这个顺序,我们就可以访问到每个节点,并且每条边会恰好被走两次(从父亲到儿子一次,从儿子到父亲一次)

由于dfs的特性,它有时候会非常的浪费时间,为什么呢?

还是刚才这张图:



如果我们把终点设在10号点,在dfs的过程中要先搜完一号点及其三个子树才能到达终点

代码大体框架:

  1. void dfs(int k){
  2. if(到达目的地或满足条件)输出解
  3. for(int i=1;i<=算符种数;++i){
  4. 保存结果//有时候不需要
  5. dfs(k+1);
  6. 回溯结果//有时候不需要
  7. }
  8. }

那么什么时候需要回溯呢?

我们先要了解回溯的目的:

  1. 我们在搜索的过程中,先选择一种可能的情况向前搜索,一旦发现选择的结果是错误的,就退一步重新选择,这就需要回溯,向前搜索一步之后将状态恢复成之前的样子

所以在解题的过程中要判断好是否需要回溯

bfs

bfs利用了一种线性数据结构,队列

bfs的核心思想是:从厨师节点开始,生成第一层节点,检查目标节点是否在目标节点中,若没有再将第一层所有的节点逐一扩展,如此往复知道发现目标节点为止

我们再拿出徐瑞帆dalao的图:



你现在还是在一号点,你还是想找到树中与一号点连通的每一个点

我们初始的时候把一号点推入队取出队尾,然后只要当前队列非空,我们就取出队头元素x,并将队头弹出

然后我们将x的所有儿子推入队列

对于图上的情况,我们将所有与x相连,并且还没入过队的点推入队列

这样我们就能够访问所有点

代码大致框架:

  1. void bfs(){
  2. q.push(head);
  3. while(!q.empty()){
  4. temp=q.front;
  5. q.pop();
  6. if(temp为目标状态)输出解
  7. if(temp不合法)continue;
  8. if(temp合法)q.push(temp+Δ);
  9. }
  10. }

IDFS

我们已经学会了dfs和bfs

然而有的问题还是使我们无法进行搜索,因为你要进行搜索的图可能是无限大的,每个点所连的边也可能是无限多的,这就使得dfs和bfs都失效了,这时候我们就需要用到idfs

我们枚举深搜的时候深度的上限,因为深度上限的限制,图中的一些边会被删掉,而图就变成了一个有限的图,我们就可以进行dfs了

举个栗子:



如果用普通的dfs,这显然是一个无解的情况,你将会陷入无限的左子树中

这时,我们设一个深度d,每次搜到第d层就返回搜其他的分支。如果在d层没搜到答案则d++,从头再搜

然而这个算法有一个很明显的缺陷,有一些非答案点要重复搜好几遍,这造成了极大的浪费

于是我们有了IDA*

A*

在看IDA* 之前,我们先了解A*

搜索算法经常运行效率很低,为了提高效率,我们可以使用A*算法

我们对每个点定义一个估价函数f(x)=g(x)+h(x)

g(x)表示从起始点到x的实际代价

h(x)表示估计的从x到结束点的代价,并要求h(x)小于等于从x到结束点的实际代价

那么每次我们从可行点集合中找到f(x)最小的x,然后搜索他

这个过程可以用优先队列(即堆)实现

这样的话可以更快地到达结束点,并保证到达结束点时走的是最优路径

为什么要求h(x)小于等于实际代价呢?

因为如果h(x)大于实际代价的话,可能以一条非最优的路径走到结束点,导致答案变大

举个栗子:用A*做的八数码难题

  1. #include<map>
  2. #include<queue>
  3. #include<iostream>
  4. #include<algorithm>
  5. using namespace std;
  6. int dx[]={-1,0,0,1},dy[]={0,-1,1,0};
  7. int final[]={-1,0,1,2,5,8,7,6,3};
  8. struct node
  9. {
  10. int state,g,h;
  11. node(int _state,int _g)
  12. {
  13. state=_state;
  14. g=_g;
  15. h=0;
  16. int tmp=state;
  17. for(int i=8;i>=0;i--)
  18. {
  19. int a=tmp%10;tmp/=10;
  20. if(a!=0)h+=abs((i/3)-(final[a]/3))+abs((i%3)-(final[a]%3));
  21. }
  22. }
  23. };
  24. bool operator<(node x,node y)
  25. {
  26. return x.g+x.h>y.g+y.h;
  27. }
  28. priority_queue<node>q;
  29. map<int,bool>vis;
  30. int main()
  31. {
  32. int n;
  33. cin>>n;
  34. q.push(node(n,0));
  35. vis[n]=1;
  36. while(!q.empty())
  37. {
  38. node u=q.top();
  39. int c[3][3],f=0,g=0,n=u.state;q.pop();
  40. if(u.state==123804765)
  41. {
  42. cout<<u.g<<endl;
  43. return 0;
  44. }
  45. for(int i=2;i>=0;i--)
  46. for(int j=2;j>=0;j--)
  47. {
  48. c[i][j]=n%10,n/=10;
  49. if(!c[i][j])f=i,g=j;
  50. }
  51. for(int i=0;i<4;i++)
  52. {
  53. int nx=f+dx[i],ny=g+dy[i],ns=0;
  54. if(nx<0||ny<0||nx>2||ny>2)continue;
  55. swap(c[nx][ny],c[f][g]);
  56. for(int i=0;i<3;i++)
  57. for(int j=0;j<3;j++)
  58. ns=ns*10+c[i][j];
  59. if(!vis.count(ns))
  60. {
  61. vis[ns]=1;
  62. q.push(node(ns,u.g+1));
  63. }
  64. swap(c[nx][ny],c[f][g]);
  65. }
  66. }
  67. }

这是bfs做法



这是A*做法

很明显,A*比bfs快多了

值得注意的是,A*只能在有解的情况下使用

IDA*

在进行IDFS的时候,我们也可以用A*进行搜索

如果在当前深度限制下搜到了结束状态,我们就可以直接输出答案

代码大体框架:

  1. //1代表墙,0代表空地,2代表终点
  2. int G[maxn][maxn];
  3. int n, m;
  4. int endx, endy;
  5. int maxd;
  6. const int dx[4] = { -1, 1, 0, 0 };
  7. const int dy[4] = { 0, 0, -1, 1 };
  8. namespace ida
  9. {
  10. bool dfs(int x, int y, int d);
  11. inline int h(int x, int y);
  12. bool ida_star(int x, int y, int d)
  13. {
  14. if (d == maxd) //是否搜到答案
  15. {
  16. if (G[x][y] == 2)
  17. return true;
  18. return false;
  19. }
  20. int f = h(x, y) + d; //评估函数
  21. if (f > maxd) //maxd为最大深度
  22. return false;
  23. //尝试向左,向右,向上,向下走
  24. for (int i = 0; i < 4; i++)
  25. {
  26. int next_x = x + dx[i];
  27. int next_y = y + dy[i];
  28. if (next_x > n || next_x < 1 || next_y > m || next_y < 1 || G[next_x][next_y] == 1)
  29. continue;
  30. if (ida_star(next_x, next_y, d + 1))
  31. return true;
  32. }
  33. return false;
  34. }
  35. inline int h(int x, int y)
  36. {
  37. return abs(x - endx) + abs(y - endy);
  38. }
  39. }

浅谈DFS,BFS,IDFS,A*等算法的更多相关文章

  1. 浅谈DFS序

    浅谈DFS序 本篇随笔简要讲解一下信息学奥林匹克竞赛中有关树的DFS序的相关内容. DFS序的概念 先来上张图: 树的DFS序,简单来讲就是对树从根开始进行深搜,按搜到的时间顺序把所有节点打上时间戳. ...

  2. 浅谈dfs

    搜索(dfs) 搜索分为bfs与dfs 他们的算法思路都是相同的--穷举 可以说,搜索是万能的,但是复杂度往往是指数级的,往往是穷途末路才用的最后方案 dfs dfs核心操作:回溯+前进 想想你第一次 ...

  3. MMORPG战斗系统随笔(二)、浅谈场寻路Flow Field PathFinding算法

    转载请标明出处http://www.cnblogs.com/zblade/ 今天给大家带来一篇游戏中寻路算法的博客.去年,我加入一款RTS的游戏项目,负责开发其中的战斗系统,战斗系统的相关知识,属于游 ...

  4. 浅谈双流水线调度问题以及Jhonson算法

    引入:何为流水线问题 有\(n\)个任务,对于每个任务有\(m\)道工序,每个任务的\(m\)道工序必须在不同的m台机器上依次完成才算把这个任务完成,在前\(i-1\)道工序完成后才能去完成第\(i\ ...

  5. 【转】浅谈对主成分分析(PCA)算法的理解

    以前对PCA算法有过一段时间的研究,但没整理成文章,最近项目又打算用到PCA算法,故趁热打铁整理下PCA算法的知识.本文观点旨在抛砖引玉,不是权威,更不能尽信,只是本人的一点体会. 主成分分析(PCA ...

  6. 浅谈URLEncoder编码算法

    一.为什么要用URLEncoder 客户端在进行网页请求的时候,网址中可能会包含非ASCII码形式的内容,比如中文. 而直接把中文放到网址中请求是不允许的,所以需要用URLEncoder编码地址, 将 ...

  7. 浅谈Hex编码算法

    一.什么是Hex 将每一个字节表示的十六进制表示的内容,用字符串来显示. 二.作用 将不可见的,复杂的字节数组数据,转换为可显示的字符串数据 类似于Base64编码算法 区别:Base64将三个字节转 ...

  8. 浅谈Base64编码算法

    一.什么是编码解码 编码:利用特定的算法,对原始内容进行处理,生成运算后的内容,形成另一种数据的表现形式,可以根据算法,再还原回来,这种操作称之为编码. 解码:利用编码使用的算法的逆运算,对经过编码的 ...

  9. 浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

    http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的 ...

随机推荐

  1. seajs源码学习(一)

    今天是2015年12月4日,天气较为阴冷.(习惯性记下日期和天气,总要留些回忆给以后) 学习的最佳捷径是模仿,所以如果想快速提高javascript技术,捷径就是去读大神们的优秀源码.就像我们学说话一 ...

  2. 大数据基石——Hadoop与MapReduce

    本文始发于个人公众号:TechFlow 近两年AI成了最火热领域的代名词,各大高校纷纷推出了人工智能专业.但其实,人工智能也好,还是前两年的深度学习或者是机器学习也罢,都离不开底层的数据支持.对于动辄 ...

  3. 浅谈月薪3万 iOS程序员 的职业规划与成长!(进阶篇)

    前言: 干了这么多年的iOS,虽然接触了许多七七八八的东西.技术,但是感觉本身iOS却没有什么质的飞越,可能跟自己接触的项目深度有关,于是决定在学习其他技术的同时,加强自己在iOS方面的学习,提高自己 ...

  4. Java 连接 SQL Server 数据库

    //连接数据库 public Connection getConnection(){ //url为绝对路径 String url="jdbc:sqlserver://127.0.0.1:14 ...

  5. $Noip2016/Luogu2822$ 组合数问题

    $Luogu$ 看这题题解的时候看到一个好可爱的表情(●'◡'●)ノ♥ $Sol$ 首先注意到这题的模数是$k$.然而$k$并不一定是质数,所以不能用$C_n^m=\frac{n!}{m!(n-m)! ...

  6. 03_常用的JS正则表达式54种形式类型

    1.由数字.26个英文字母或者下划线组成的字符串: ^[0-9a-zA-Z_]{1,}$ 2.非负整数(正整数 + 0 ): ^/d+$ 3. 正整数: ^[0-9]*[1-9][0-9]*$ 4.非 ...

  7. MinIO 搭建使用

    MinIO简介¶ MinIO 是一款基于Go语言的高性能对象存储服务,在Github上已有19K+Star.它采用了Apache License v2.0开源协议,非常适合于存储大容量非结构化的数据, ...

  8. react根据传参的不同动态注册不同的子组件

    上一篇文章介绍了关于Vue如何根据传参的不同动态注册不同的子组件,实现过程请查阅Vue.extend动态注册子组件,由Vue的这个功能我就自然联想到了使用react该如何实现同样的功能呢.其实,用re ...

  9. String类方法的使用

    String类的判断功能: boolean equals(Object obj)  //比较字符串内容是否相同(区分大小写). boolean equalsIgnoreCase(String str) ...

  10. Asp.Net Core 3.0 Kestrel服务器下 高性能 WebSocket Server

    最近研究.net core 的各种高性能类型,内存池之类的东西,基于kestrel 服务器的websocket ,写个例子练下手 把原生的Websocket用ArrayPool<T>,Me ...