本人在另外两篇博客中也记录了有关搜索的内容
链接–bfs
链接—dfs
dfs
算法过程
状态(A)
- 判断当前状态是否满足题目需要,满足则进行保存、比较、输出等操作
- 判断当前状态是否合法(题目要求、数组越界等)
- 往下走一层(递归调用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 2 3 和 3 2 1是一种情况,设置一个条件使得 前一个数字必须小于等于后一个数字。
- 数字可以重复使用
- 加一个搜索条件, 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;
}
剪枝了的算法
-
搜索的起始位置:
直接从arr[step - 1]开始, -
搜索的终止位置:
填的盒子的数字,不能大于剩余能填数字的平均数
例如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]要小 -
加一个参数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(上、下、左、右 这四个方向谁先都行),遍历四个方向尝试所有情况,如果合法就进入下一层
- 对于每条合法路径, min不断更新,求最小步数
- 判断退出条件:
x == n - 1 && y == n - 1
- 注意事项
起点 记得标记,
标记数组为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内容
队列实现
- 我们会定义一个结构体,设置一个结构体队列(存储当前x,y,当前步数)
- 先得设置起点位置加入队列,并标记起点已经走过。
- 每次我们都是根据队首元素扩展的(记得弹出队首元素)
- 标记已访问过的点(不用恢复,bfs原理:层层扩展)
- 设置一个变量标记循环可以结束
#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;
}
数组实现
- 我们依旧会定义一个结构体,但不再设置队列,而是设置一个结构体数组,设置两个变量,模拟出队入队过程
- head指向(相当于队列头的元素), tail指向(相当于队尾,即最后一个元素)
- 出队: head + 1
入队: tail + 1 - 注意最后输出的是
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
- 在这里,我们没有打印终点坐标,直接找终点的父亲(因此我们传入的参数为head)
- 在写函数时,我们调用递归来找父亲的父亲,一定要有结束条件,在这里为
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);
}
}