搜索 (二)

本人在另外两篇博客中也记录了有关搜索的内容
链接–bfs
链接—dfs

dfs

算法过程

状态(A)

  1. 判断当前状态是否满足题目需要,满足则进行保存、比较、输出等操作
  2. 判断当前状态是否合法(题目要求、数组越界等)
  3. 往下走一层(递归调用dfs, 记得 return)

一道例题: 整数划分(体会剪枝)

问题描述:

一个正整数可以划分为多个正整数的和,比如n=6时: 6;1+5;2+4;3+3;2+2+2;1+1+4;1+2+3;1+1+1+3;1+1+2+2;1+1+1+1+2;1+1+1+1+1+1 共有十一种划分方法。   给出一个正整数,问有多少种划分方法。

搜索剪枝
去除一些显然不可能的情况,减少搜索次数

基本思路

正整数n可以看成n个小球,每一个小球代表数字一,观察可知划分即为将这n个小球放入k的盒子里面,且每一个盒子不能为空,则k依次为从1到n,k=n时即为n个1相加。每次搜索k个盒子

  1. 1 2 3 和 3 2 1是一种情况,设置一个条件使得 前一个数字必须小于等于后一个数字。
  2. 数字可以重复使用
  3. 加一个搜索条件, k 个盒子(范围1-n)

很朴素的一种写法(未剪枝)

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
int arr[10]; //a[i] = k, 表示 数字i出现了k次
bool book[10];
int n, sum;

void dfs(int step, int k){
    if(step == k + 1){
        int num = 0;
        for(int i = 1; i <= k; i ++){
            num += arr[i];
        }
        if(num == n) sum ++;
        return ; //表示一个返回的过程

    }
    for(int i = 1; i <= n; i ++){
        arr[step] = i;
        if(arr[step] >= arr[step - 1]){
            dfs(step + 1, k);
        }
    }
}

int main(void){
    cin >> n;
    for(int i = 1; i <= n; i ++){
        dfs(1, i);
    }
    cout << sum << endl;
    return 0;
}

剪枝了的算法

  1. 搜索的起始位置:
    直接从arr[step - 1]开始,

  2. 搜索的终止位置:
    填的盒子的数字,不能大于剩余能填数字的平均数
    例如1 2 3 4,(下标: 1 2 3 4)为一组解,那么在枚举arr[3]的时候,此时sum = 7,sum/(k-step+1)=3.5,则arr[3]不能超过3,否则arr[4]必定比arr[3]要小

  3. 加一个参数m,表示剩余总数, 有了剩余总数,我们在dfs判断是否为合法方案是,不用算总数是否等于n了,直接m == 0判断即可。

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
int arr[10]; //a[i] = k, 表示 数字i出现了k次

int n, sum;

void dfs(int step, int k, int m){
    if(step == k + 1){
        if(m == 0) sum ++;
        return ; //表示一个返回的过程

    }
    for(int i = arr[step - 1]; i <= m / (k- step + 1); i ++){
        arr[step] = i;
        dfs(step + 1, k, m - i);
    }
}

int main(void){
    cin >> n;
    arr[0] = 1;//我们会用到step - 1, 直接让第一次dfs中的i从1开始,而不是0
    for(int i = 1; i <= n; i ++){
        dfs(1, i, n);
    }
    cout << sum << endl;
    return 0;
}

例题二:迷宫问题

问题描述:

//定义一个 n 行 n 列 的二维数组
例如当n等于5时有
0  1  0  0  0
0  1  0  1  0
0  0  0  0  0
0  1  1  1  0
0  0  0  1  0
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。

基本思路

从起点出发,每次按照 顺序1(上、下、左、右 这四个方向谁先都行),遍历四个方向尝试所有情况,如果合法就进入下一层

  1. 对于每条合法路径, min不断更新,求最小步数
  2. 判断退出条件: x == n - 1 && y == n - 1
  3. 注意事项
    起点 记得标记,
    标记数组为book,不能改变原数组的值。
    每次记得判断边界,该点是否合法等细节

代码实现

#include <iostream>
#include <cstdio>
using namespace std;
int n;
int arr[1010][1010];
int book[1010][1010];
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int ans = 0x3fffffff;
void dfs(int x, int y, int step){
    if(x == n - 1 && y == n - 1){
        if(step < ans){
            ans = step;
        }
        return ;
    }
    for(int i = 0; i < 4; i ++){
        int xx = x + dx[i], yy = y + dy[i];
        if(xx < 0 || xx > n - 1 || yy < 0 || yy > n -1) continue;
        if(arr[xx][yy] == 0 && book[xx][yy] == 0){
            book[xx][yy] = 1;
            dfs(xx, yy, step + 1);
            book[xx][yy] = 0;
        }
    }

}

int main(void){
    cin >> n;
    for(int i = 0; i < n; i ++){
        for(int j = 0; j < n; j ++){
            cin >> arr[i][j];
        }
    }
    book[0][0] = 1;
    dfs(0, 0, 0);
    cout << ans;
    return 0;
}

bfs

基本思想

一层一层的解决,依次找出一步可以到达的点,两步,三步,…可以到达的点。将每次走过的点加入队列来进行扩展。

bfs解决迷宫最小步数问题

题目描述同上文dfs内容

队列实现

  1. 我们会定义一个结构体,设置一个结构体队列(存储当前x,y,当前步数)
  2. 先得设置起点位置加入队列,并标记起点已经走过。
  3. 每次我们都是根据队首元素扩展的(记得弹出队首元素)
  4. 标记已访问过的点(不用恢复,bfs原理:层层扩展)
  5. 设置一个变量标记循环可以结束
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int n;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int book[330][330];
int arr[330][330];

struct f{
    int x;
    int y;
    int s;
};
queue <f> qu; //定义一个结构体队列

void bfs(){
    struct f a;
    a.x = 0;
    a.y = 0;
    a.s = 0;
    qu.push(a);
    book[0][0] = 1;
    while(!qu.empty())
    {
        int k = 0; //作为我们可以结束该循环的标记
       // printf("k = %d\n", k);
        struct f b = qu.front();
        qu.pop();
        for(int i = 0; i < 4; i ++){
            struct f c;
            c.x = b.x + dx[i];
            c.y = b.y + dy[i];
            c.s = b.s + 1;
            if(c.x < 0 || c.x > n - 1 || c.y < 0 || c.y > n - 1) continue;
            if(book[c.x][c.y] == 0 && arr[c.x][c.y] == 0){
                book[c.x][c.y] = 1;
                qu.push(c);
           //     printf("c.x = %d\n", c.x);
                if(c.x == n - 1 && c.y == n - 1){
                    k = 1;
                    cout << c.s;
                    break;
                }
            }
        }
        if(k) break;
    }

}

int main(void){
    cin >> n;
    for(int i = 0; i < n; i ++){
        for(int j = 0; j < n; j ++){
            cin >> arr[i][j];
        }
    }
    bfs();
    return 0;
}

数组实现

  1. 我们依旧会定义一个结构体,但不再设置队列,而是设置一个结构体数组,设置两个变量,模拟出队入队过程
  2. head指向(相当于队列头的元素), tail指向(相当于队尾,即最后一个元素)
  3. 出队: head + 1
    入队: tail + 1
  4. 注意最后输出的是 mat[tail - 1].s
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int n;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int book[330][330];
int arr[100][100];

struct f{
    int x;
    int y;
    int s;
}mat[10005];


void bfs(){
    int head = 1;
    int tail = 1;
    mat[head].x = 0, mat[head].y = 0, mat[head].s = 0;
    tail ++;
    book[0][0] = 1;
    while(head < tail)
    {
        int k = 0; //作为我们可以结束该循环的标记
        for(int i = 0; i < 4; i ++){
            int xx = mat[head].x + dx[i], yy = mat[head].y + dy[i];
            if(xx < 0 || xx > n - 1 || yy < 0 || yy > n - 1) continue;
            if(book[xx][yy] == 0 && arr[xx][yy] == 0){
                book[xx][yy] = 1;
                mat[tail].x = xx, mat[tail].y = yy;
                mat[tail].s = mat[head].s + 1;
                tail ++;
                if(xx == n - 1 && yy == n - 1){
                    k = 1;
                    cout << mat[tail - 1].s;
                    break;
                }
            }
        }
        if(k) break;
        head ++;
    }

}

int main(void){
    cin >> n;
    for(int i = 0; i < n; i ++){
        for(int j = 0; j < n; j ++){
            cin >> arr[i][j];
        }
    }
    bfs();
    return 0;
}

注意
如深搜:递归调用过程,一定要写return
广搜不用写。

迷宫问题输出路径

基本思路

结构体增加一个变量 f ,记录当前点的父亲点的坐标,从终点开始,递归找到最短路径上的每一个点的坐标并打印,在这里我们写了一个print函数

  1. 起点没有父亲,记为 -1
  2. 在这里,我们没有打印终点坐标,直接找终点的父亲(因此我们传入的参数为head)
  3. 在写函数时,我们调用递归来找父亲的父亲,一定要有结束条件,在这里为mat[x].f == -1, 结束

下面我们写的代码为起点到终点打印

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int n;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int book[330][330];
int arr[100][100];

struct f{
    int x;
    int y;
    int s;
    int f;
}mat[10005];

void print(int x){
    if(mat[x].f != -1){
        print(mat[x].f); //找到x点的父亲
        printf("(%d , %d)\n", mat[x].x, mat[x].y);//打印当前点
    }
}


void bfs(){
    int head = 1;
    int tail = 1;
    mat[head].x = 0, mat[head].y = 0, mat[head].s = 0;
    mat[head].f = -1;
    tail ++;
    book[0][0] = 1;
    while(head < tail)
    {
        int k = 0; //作为我们可以结束该循环的标记
        for(int i = 0; i < 4; i ++){
            int xx = mat[head].x + dx[i], yy = mat[head].y + dy[i];
            if(xx < 0 || xx > n - 1 || yy < 0 || yy > n - 1) continue;
            if(book[xx][yy] == 0 && arr[xx][yy] == 0){
                book[xx][yy] = 1;
                mat[tail].x = xx, mat[tail].y = yy;
                mat[tail].s = mat[head].s + 1;
                mat[tail].f = head;
                tail ++;
                if(xx == n - 1 && yy == n - 1){
                    k = 1;
                    cout << mat[tail - 1].s << endl;
                    print(head);
                    break;
                }
            }
        }
        if(k) break;
        head ++;
    }

}

int main(void){
    cin >> n;
    for(int i = 0; i < n; i ++){
        for(int j = 0; j < n; j ++){
            cin >> arr[i][j];
        }
    }
    bfs();
    return 0;
}

如何从终点-1倒着打印?

void print(int x){
    if(mat[x].f != -1){
     //   print(mat[x].f); //找到x点的父亲
        printf("(%d , %d)\n", mat[x].x, mat[x].y);//打印当前点
        print(mat[x].f);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xuhx&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值