您的位置:首页 > 其它

《啊哈算法》第四章 万能的搜索

2016-04-27 20:01 316 查看
参考:《啊哈算法》

这里的搜索与图的遍历要区分开,这里只是对数的搜索进行尝试,常用于地图搜索什么,查找是否通路什么的。

深度优先搜索

问题

求出数字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最好掌握和理解
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: