《啊哈算法》第四章 万能的搜索
2016-04-27 20:01
316 查看
参考:《啊哈算法》
这里的搜索与图的遍历要区分开,这里只是对数的搜索进行尝试,常用于地图搜索什么,查找是否通路什么的。
求出数字123的全排列
额,先来暴力一个;
下面展示深搜bfs
假设你的手中有1-9九个数字,分别放到【】中,使得【】【】【】+【】【】【】=【】【】【】成立。
分析,这就是先将1-9进行全排列,再进行筛选使得条件成立的情况就可以了,暴力虽然笨拙,但是也能实现目标
问题
A被困在迷宫中,求解救
输入迷宫的大小并输入迷宫,0为空地,1为障碍物
再输入出发解救位置,输入A的位置,输出最小路径
code
简单的分析:深度优先搜索的实质是一次次进行尝试,最具代表性的引用就是数字的全排列莫属了,这里也是一样,不断地进行尝试,有可能到达目的地的路径不止一条,因此dfs能够帮助我们找到所有通向目的地的路径,记录下步长,就能查找出最短路径了。(所谓的尝试就是同样的初始条件下进行不同的选择,因此在各种情况执行后,还是需要恢复到各种初始状态,所以每次的回收很重要,每次的return至最近一次调用也是很关键)。dfs应该是能想象成兵分XX路,再兵分XX路,他们的目的地是相同的。
问题
解决上一个问题
简单分析:与dfs不同,dfs的终止条件是到达目的地就退出,结束搜索,当然人家的搜索方法也是决定了第一次到达地步长最短了。因此,bfs同样也是能够找出多条到达目的地的路径,但是第一次到达的时候用的步长一定是最短的。bfs应该是能够想象成一个小圆点不断的向四周膨胀至结束位置的过程。
给出一个二维数组,并用字符填充,其中.G#分别代表空地,敌人,不能炸坏的墙。请问在哪里放置炸弹才能消灭最多的敌人。
简单暴力:循环遍历每一个点,对这个点的攻击范围内的敌人进行统计记录,并随时更新。(暴力鲁棒性不足,因为这样的话就是默认炸弹是可以放在任何一块空地上的,但是事实并非如此,即有些空地是不能放置炸弹的,即有可能某块或几块空地被墙包围着,根本就无法进入空地放置炸弹)
测试:
(与第一个暴力的数据有改动)
测试:
这里的搜索与图的遍历要区分开,这里只是对数的搜索进行尝试,常用于地图搜索什么,查找是否通路什么的。
深度优先搜索
问题求出数字123的全排列
额,先来暴力一个;
#include <cstdio> int main() { for(int i=1; i<=3; i++) { for(int j=1; j<=3; j++) { for(int k=1; k<=3; k++) { if(i!=j&&i!=k&&j!=k) { printf("%d %d %d \n",i,j,k); } } } } return 0; }
下面展示深搜bfs
#include <cstdio> int a[4],book[4]; void dfs(int step) { if(step==4) { for(int i=1; i<=3; i++) { printf("%d ",a[i]); } printf("\n"); return ; } for(int i=1; i<=3; i++) { if(book[i]==0) { a[step]=i; book[i]=1; dfs(step+1); book[i]=0; } } } int main() { dfs(1); return 0; }
dfs公式
void dfs(int step) { 判断边界 尝试每一种可能 for(int i=1;i<=n;i++)//尝试每一种可能意味深长呀 { 继续下一步 dfs(step+1); } 返回 }
嗯,我是体会了很长一段时间才体会到尝试每一种可能是什么意思,。,。,人笨呀,。,。每一个循环都不会浪费,到底后,进行返回。大分支结束大返回,回收发出去的牌,小分支结束小返回,回收发出去的牌。(递归的特点)
问题假设你的手中有1-9九个数字,分别放到【】中,使得【】【】【】+【】【】【】=【】【】【】成立。
分析,这就是先将1-9进行全排列,再进行筛选使得条件成立的情况就可以了,暴力虽然笨拙,但是也能实现目标
#include <cstdio> int a[10],book[10]; void dfs(int step) { if(step==10) { if(a[1]*100+a[2]*10+a[3]+a[4]*100+a[5]*10+a[6]==a[7]*100+a[8]*10+a[9]) { printf("%d%d%d %d%d%d %d%d%d\n",a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]); //这个是返回最近一次调用dfs的关键语句 } return ; } for(int i=1; i<=9; i++) { if(book[i]==0) { a[step]=i; book[i]=1; dfs(step+1);//不能写成step++ book[i]=0;//一定要注意回收 } } } int main() { dfs(1); return 0; }
问题
A被困在迷宫中,求解救
输入迷宫的大小并输入迷宫,0为空地,1为障碍物
再输入出发解救位置,输入A的位置,输出最小路径
code
简单的分析:深度优先搜索的实质是一次次进行尝试,最具代表性的引用就是数字的全排列莫属了,这里也是一样,不断地进行尝试,有可能到达目的地的路径不止一条,因此dfs能够帮助我们找到所有通向目的地的路径,记录下步长,就能查找出最短路径了。(所谓的尝试就是同样的初始条件下进行不同的选择,因此在各种情况执行后,还是需要恢复到各种初始状态,所以每次的回收很重要,每次的return至最近一次调用也是很关键)。dfs应该是能想象成兵分XX路,再兵分XX路,他们的目的地是相同的。
#include <cstdio> int c[51][51],book[51][51]; int next[4][2]= {{0,1},{1,0},{0,-1},{-1,0}};//这里一定要和数学上的坐标进行区分,x,y分别代表行和列 int a,b; int min=999999999; int startx,starty,p,q; void dfs(int x,int y,int step) { int tx,ty; if(x==p&&y==q) { if(step<min) { min=step; } return ;//判断边界的返回不能丢 } for(int i=0; i<=3; i++) { tx=x+next[i][0]; ty=y+next[i][1]; if(tx<1||tx>a||ty<1||ty>b) {//判断是否越界 continue; } if(book[tx][ty]==0&&c[tx][ty]==0) { book[tx][ty]=1; dfs(tx,ty,step+1); book[tx][ty]=0;//收回不能丢 } } } int main() { scanf("%d%d",&a,&b); for(int i=1; i<=a; i++) { for(int j=1; j<=b; j++) { scanf("%d",&c[i][j]); } } scanf("%d%d%d%d",&startx,&starty,&p,&q); book[startx][starty]=1;//这里就和全排列有点不一样,因为这里进去之后就要发生改变,而全排列进去之后不改变 dfs(startx,starty,0); printf("%d\n",min); return 0; }
测试数据
5 4 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 1 1 4 3
广度优先搜索
同样是对四周进行扩展,只是bfs是所有能扩展的点进入队列,不像深搜,不到最后不回头问题
解决上一个问题
简单分析:与dfs不同,dfs的终止条件是到达目的地就退出,结束搜索,当然人家的搜索方法也是决定了第一次到达地步长最短了。因此,bfs同样也是能够找出多条到达目的地的路径,但是第一次到达的时候用的步长一定是最短的。bfs应该是能够想象成一个小圆点不断的向四周膨胀至结束位置的过程。
#include <cstdio> #define N 51 int c ,book ;//保存地图和标记数组,一般标记数组和地图一一对应 int next[4][2]= {{0,1},{1,0},{0,-1},{-1,0}};//这个探索方向是很有味道的 typedef struct Que { int x; int y; int step; };//用一个结构体模拟链表进行存放,出队和入队 int main() { Que que[2501];//为什么要开这么大的数组?最坏情况是走完地图上的所有点才到达目的地,但是一般不会出现这种情况,但是我们也无法知道每次究竟要扩展到哪里(多少步,只能概全了) int a,b; scanf("%d%d",&a,&b);//输入地图大小 for(int i=1; i<=a; i++) { for(int j=1; j<=b; j++) { scanf("%d",&c[i][j]);//输入地图 } } int startx,starty,p,q; scanf("%d%d%d%d",&startx,&starty,&p,&q);//输入起始地址和目标地址 int head=1; int tail=1;//初始链表 book[head][head]=1;//将出发地址标记,表示已经访问过了 que[head].x=startx; que[head].y=starty;//出发地址入队 que[head].step=0; tail++;//与我们一般情况对应,一般是tail指向队尾的下一位置方便添加 int flag=0; while(head<tail) { for(int i=0; i<=3; i++) {//在队列的ead处的点四个相邻方向扩展 int tx,ty; tx=que[head].x+next[i][0]; ty=que[head].y+next[i][1]; if(tx<1||tx>a||ty<1||ty>b) {//判断是否越界,若不合条件即认为已经到了边界,马上退出执行下一次循环 continue; } if(c[tx][ty]==0&&book[tx][ty]==0) {//判断扩展到的点是否符合条件 que[tail].x=tx; que[tail].y=ty;//符合条件则入队 book[tx][ty]=1;//并标记已经访问过了 que[tail].step=que[head].step+1;//扩展步数等于head(父步数)+1; tail++;//用完再向后移动,方便下一次的入队 } if(tx==p&&ty==q) {//到达目的地后退出循环,因为是bfs,第一次到达目的地所用步数应该是最少的,不需要尝试,直接退出 flag=1; break; } } if(flag) { break;//两个退出是因为有两个循环,而我们是到达目的地后一次性退出所有循环 } head++;//每一个head扩展四个方向上的点,(四个点),然后再向下一个点移动 } printf("%d\n",que[tail-1].step);//tail++每次都执行了的,因此收尾时tail仍是指向的队尾的下一位置,-1进行提前 return 0; }
炸弹人
问题给出一个二维数组,并用字符填充,其中.G#分别代表空地,敌人,不能炸坏的墙。请问在哪里放置炸弹才能消灭最多的敌人。
简单暴力:循环遍历每一个点,对这个点的攻击范围内的敌人进行统计记录,并随时更新。(暴力鲁棒性不足,因为这样的话就是默认炸弹是可以放在任何一块空地上的,但是事实并非如此,即有些空地是不能放置炸弹的,即有可能某块或几块空地被墙包围着,根本就无法进入空地放置炸弹)
#include <cstdio> #define N 20 char a ;//创建二维数组准备填充数据 int main() { int b,c; scanf("%d%d",&b,&c);//输入准备创建地图的规模 for (int i=0; i<b; i++) { scanf("%s",a[i]);//创建地图,按行读入 } int max=0; int x,y; int p,q; for(int i=0; i<b; i++) { for(int j=0; j<c; j++) {//二重循环遍历地图,这样是通过每一个都进行遍历,并且统计每个点的消灭敌人数,更新 if(a[i][j]=='.') {//判断空地然后开始搜索 int sum=0; x=i; y=j; while(a[x][y]!='#') {//遇到墙就停止搜索 if(a[x][y]=='G') { sum++; } x--;//向上搜索 if(x<0||x>=b) {//判断边界 break; } } x=i; y=j; while(a[x][y]!='#') { if(a[x][y]=='G') { sum++; } x++;//向下搜索 if(x<0||x>=b) { break; } } x=i; y=j; while(a[x][y]!='#') { if(a[x][y]=='G') { sum++; } y--;//向右搜索 if(y<0||y>=c) { break; } } x=i; y=j; while(a[x][y]!='#') { if(a[x][y]=='G') { sum++; } y++;//向左搜索 if(y<0||y>=c) { break; } } if(sum>max) {//更新max max=sum; p=i; q=j; } } } } printf("在(%d,%d)位置最多能消灭%d\n",p,q,max); return 0; }
测试数据
13 13 ############# #GG.GGG#GGG.# ###.#G#G#G#G# #.......#..G# #G#.###.#G#G# #GG.GGG.#.GG# #G#.#G#.#.### ##G...G.....# #G#.#G###.#G# #...G#GGG.GG# #G#.#G#G#.#G# #GG.GGG#G.GG# #############
针对鲁棒性不强有以下解决方案,先要判断出能到达哪些空地,然后再对该块空地四周搜索统计敌人数量,但是怎样判断能否到达空地尼?嗯,bfs或者dfs。用bfs或dfs来枚举所有小人可以到达的点,然后在这些可以到达的点上分别统计可以消灭的敌人数
dfs展示
#include<cstdio> #define N 21 int book ; int next[4][2]= {{0,1},{1,0},{0,-1},{-1,0}}; char maps ; int max=0; int p,q; int a,b,startx,starty; int getnum(int i,int j) { int sum=0; int x; int y; x=i; y=j; while(maps[x][y]!='#') {//遇到墙就停止搜索 if(maps[x][y]=='G') { sum++; } x--;//向上搜索 if(x<0||x>=a) {//判断边界 break; } } x=i; y=j; while(maps[x][y]!='#') { if(maps[x][y]=='G') { sum++; } x++;//向下搜索 if(x<0||x>=a) { break; } } x=i; y=j; while(maps[x][y]!='#') { if(maps[x][y]=='G') { sum++; } y--;//向右搜索 if(y<0||y>=b) { break; } } x=i; y=j; while(maps[x][y]!='#') { if(maps[x][y]=='G') { sum++; } y++;//向左搜索 if(y<0||y>=b) { break; } } return sum; } void dfs(int x,int y) { int sum=getnum(x,y); if(sum>max) { max=sum; p=x; q=y; } int tx,ty; for(int i=0; i<=3; i++) { tx=x+next[i][0]; ty=y+next[i][1]; if(tx<0||tx>a-1||ty<0||ty>b-1) { continue; } if(maps[tx][ty]=='.'&&book[tx][ty]==0) { book[tx][ty]=1; dfs(tx,ty); } } } int main() { scanf("%d%d%d%d",&a,&b,&startx,&starty); for(int i=0; i<a; i++) { scanf("%s",maps[i]); } dfs(startx,starty); printf("在(%d,%d)位置最多能消灭%d\n",p,q,max); return 0; }
测试:
13 13 3 3 ############# #GG.GGG#GGG.# ###.#G#G#G#G# #.......#..G# #G#.###.#G#G# #GG.GGG.#.GG# #G#.#G#.#.#.# ##G...G.....# #G#.#G###.#G# #...G#GGG.GG# #G#.#G#G#.#G# #GG.GGG#G.GG# #############
(与第一个暴力的数据有改动)
bfs展示
#include <cstdio> #define N 21 char maps ; int book ; int next[4][2]= {{0,1},{1,0},{0,-1},{-1,0}}; int max=0; int a,b,startx,starty; typedef struct Que { int x; int y; }; int getnum(int i,int j) { int sum=0; int x; int y; x=i; y=j; while(maps[x][y]!='#') {//遇到墙就停止搜索 if(maps[x][y]=='G') { sum++; } x--;//向上搜索 if(x<0||x>=a) {//判断边界 break; } } x=i; y=j; while(maps[x][y]!='#') { if(maps[x][y]=='G') { sum++; } x++;//向下搜索 if(x<0||x>=a) { break; } } x=i; y=j; while(maps[x][y]!='#') { if(maps[x][y]=='G') { sum++; } y--;//向右搜索 if(y<0||y>=b) { break; } } x=i; y=j; while(maps[x][y]!='#') { if(maps[x][y]=='G') { sum++; } y++;//向左搜索 if(y<0||y>=b) { break; } } return sum; } int main() { Que que[401]; scanf("%d%d%d%d",&a,&b,&startx,&starty); for(int i=0; i<a; i++) { scanf("%s",maps[i]); } int head=0; int tail=0; book[startx][starty]=1; que[head].x=startx; que[head].y=starty; tail++; int tx,ty; int p,q; while(head<tail) { //将所有能到的点入队,再从头到尾统计比较 int sum=getnum(que[head].x,que[head].y); if(sum>max) { max=sum; p=que[head].x; q=que[head].y; } for(int i=0; i<=3; i++) { tx=que[head].x+next[i][0]; ty=que[head].y+next[i][1]; if(tx<1||tx>=a||ty<1||ty>=b) { continue; } if(maps[tx][ty]=='.'&&book[tx][ty]==0) { book[tx][ty]=1; que[tail].x=tx; que[tail].y=ty; tail++; } } head++; } printf("在(%d,%d)位置最多能消灭%d\n",p,q,max); return 0; }
测试:
13 13 3 3 ############# #GG.GGG#GGG.# ###.#G#G#G#G# #.......#..G# #G#.###.#G#G# #GG.GGG.#.GG# #G#.#G#.#.#.# ##G...G.....# #G#.#G###.#G# #...G#GGG.GG# #G#.#G#G#.#G# #GG.GGG#G.GG# #############
总结:
对于一维数组的搜索,二维数组的搜索都是很有特点的,不管是bfs还是dfs,,,,不过我发现bfs最好掌握和理解相关文章推荐
- Codeforces Beta Round #11 A. Increasing Sequence 贪心
- android---高德地图(1)---显示一张简单地图
- Docker网络基础配置--RHEL7.2
- centos 下的任务计划 -- cron
- android---高德地图(2)---定位-显示小蓝点
- 团队作业(四)
- Android Glide图片加载框架图片变色变绿解决方法
- uva 10603 Fill 搜索
- Android AssetManager 简读<2>
- 详解css中relative,absolute,float用法
- (转)RSA加密解密及数字签名Java实现
- android---高德地图(3)点击获得目的经纬度,地理编码
- [转]JAVA String 类
- android---高德地图(4)路线规划
- 改变Activity启动时的默认动画
- tez跑任务报错
- 在C++中将一个GUID变量转换成为string变量
- 剑指offer(39):数字在排序数组中出现的次数
- android---高德地图(5)导航界面(语音播报)实现
- android---简单语音合成