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

参考:《啊哈算法》

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

深度优先搜索

问题
求出数字123的全排列

额,先来暴力一个;

#include 
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 
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 
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 
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(stepreturn ;//判断边界的返回不能丢
    }
    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 
#define N 51
int c[N][N],book[N][N];//保存地图和标记数组,一般标记数组和地图一一对应
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(headfor(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 
#define N 20
char a[N][N];//创建二维数组准备填充数据
int main() {
    int b,c;
    scanf("%d%d",&b,&c);//输入准备创建地图的规模
    for (int i=0; i"%s",a[i]);//创建地图,按行读入
    }
    int max=0;
    int x,y;
    int p,q;
    for(int i=0; ifor(int j=0; 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
#define N 21
int book[N][N];
int next[4][2]= {{0,1},{1,0},{0,-1},{-1,0}};
char maps[N][N];
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"%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 
#define N 21
char maps[N][N];
int book[N][N];
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"%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//将所有能到的点入队,再从头到尾统计比较
        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最好掌握和理解

你可能感兴趣的:(啊哈算法)