搜索与搜索剪枝
搜索
- 通过不停的试探去寻找解的一种算法
- 与其说是一种算法,不如说是一种方法
- 基础的方法有暴力的搜索法,深搜,广搜三种
- 更高级的有IDDFS(迭代加深搜索),DBFS(双向搜索),A* ,IDA*等等
深度优先搜索(dfs)
(一条道走到黑,走不了了再倒回去)
算法过程:
void dfs(状态A)
- 判断当前的状态是否合法。合法则继续执行,否则则回到上次调用。
- 先下走一层,也就是调用dfs(状态A+Δ);
输出n个数的全排列(dfs)
方法一: 可以写n重for循环(暴力搜索法)
方法二: 也可以用stl里面的next_permutation
- 递归的来想这个问题,以1,2,3,4为例
- 如果第一个数固定为1的话
- 后面就跟着2,3,4的全排列——所以就相当于是原问题的子问题的求解
void dfs(已经固定了前k-1个数
,剩下的数的全排列,是哪k-1个数通过标记该数字用没用过来显示,当前这一步的任务就是把第k个放上去){
- 如果已经求出来了
k>n
输出,返回 - 否则i从1到n循环
- 如果
i没有用过
,那么就将i固定
在当前
位置上,并调用dfs(k+1)
- 在·dfs(k+1)后需要将固定在当前位置上的
i拿走
(恢复现场)
}
#include <bits/stdc++.h> using namespace std; int n; int a[10]; //存数 int vis[10]; //判断是否被用过 void dfs(int dep){ if(dep > n){ for(int i = 1; i <= n; i++){ cout << a[i] << " "; } cout << endl; return; } for(int i = 1; i <= n; i++){ if(vis[i]) continue; //如果被用过,就continue a[dep] = i; vis[i] = 1; dfs(dep + 1); a[dep] = 0; vis[i] = 0; } } int main(){ cin >> n; dfs(1); return 0; }
扩展:输出n个数中选M个的组合(dfs)
注意: 因为是组合,所以不同顺序只算一个组合
关键: 只要后一个数比前一个数大,就能选出所有组合
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) #define endl '\n' using namespace std; typedef long long LL; int n = 5, m = 3; int a[10]; void dfs(int dep, int last){ if(dep > m){ for(int i = 1; i <= m; i++){ cout << a[i] << " "; } cout << endl; return; //初学搜索容易忘记return; } for(int i = last + 1; i <= n; i++){ //因为是从比上一个数大的开始选,所以一定没有被选过,就不需要状态数组 a[dep] = i; dfs(dep + 1, i); a[dep] = 0; //但是还是要置0 } } int main() { ios; dfs(1, 0); return 0; }
n皇后问题
题目描述
n-皇后问题是指将 n 个皇后放在
现在给定整数n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数n。
输出格式
每个解决方案占n行,每行输出一个长度为n的字符串,用来表示完整的棋盘状态。
其中”.”表示某一个位置的方格状态为空,”Q”表示某一个位置的方格上摆着皇后。
每个方案输出完成后,输出一个空行。
数据范围
1≤n≤9
样例
输入样例:
4
输出样例:
.Q.. ...Q Q... ..Q. ..Q. Q... ...Q .Q..
基本思想
由于皇后的摆放位置不能通过某种公式来确定,因此对于每个皇后的摆放位置都要进行试探
和纠正
,这就是“回溯”
的思想。
在N个皇后未放置完成前,摆放第i个皇后和摆放第i+1个皇后的试探方法是相同的,因此完全可以采用递归
的方法来处理。
由于皇后本身的特殊性质
,即一行一列只能有一个皇后,所以我们所要做的就是,从第1行开始摆放,一直摆放到n行为止。
关于判断当前皇后可不可以放:
1.是一行一行的放置皇后,所以不需要判断行冲突;
2.列冲突时,可以通过一个布尔数组,如果已经有皇后放在那里,就把布尔值设为1,如果可以放置并且没有冲突(即布尔值为0),就放置当前这个皇后,且设置为1;
3.对角线冲突时有一个特殊技巧:
主对角线: 由左上角至右下角的对角线
由于每一条主对角线(x-y)的值是一定的,每一条副对角线(x+y)值是一定的!!!(由于主对角线会出现负数,所以可以加一个偏移量列数)
所以就可以用
假设我们把第cur个皇后放在了pos[cur]
(pos[cur]存储了这个值), 那么只需要判断所检查的从前往后数第K个皇后有没有冲突就行。
#include <bits/stdc++.h> using namespace std; int n = 4; int a[20]; int lie[30], zd[30], fd[30]; void print() { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (a[i] == j) cout << "Q"; else cout << "."; } cout << endl; } cout << endl; } void dfs(int dep) { if (dep > n) { print(); return; } //枚举在dep行i列能不能放 for (int i = 1; i <= n; i++) { if (lie[i] == 0 && zd[dep - i + n] == 0 && fd[dep + i] == 0) { //dep行i列能放皇后 a[dep] = i;//向dep行i列放上皇后 lie[i] = 1; zd[dep - i + n] = 1; fd[dep + i] = 1; //对应的列和对角线占领 dfs(dep + 1); //给dep+1行放皇后 lie[i] = 0; zd[dep - i + n] = 0; fd[dep + i] = 0; //清空现场 } } } int main() { dfs(1); return 0; }
马踏棋盘
题目描述:
给一个5×5的国际象棋棋盘,国际象棋的马同样走“日”字,问:如果一个马从第一个格子开始走,那么走遍整个5×5的棋盘,有多少种?并且输出方案数。
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) #define endl '\n' using namespace std; typedef long long LL; int n = 5; int a[20]; int vis[10][10]; //vis一个二维数组两用,还存是第几个放的 void print() { for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { printf("%4d", vis[i][j]); } cout << endl; } cout << endl; } //方向数组存马可以跳的八个方向 int dir[8][2] = {{-1, -2}, {-2, -1}, {-1, +2}, {+2, -1}, {+1, -2}, {-2, +1}, {+1, +2,}, {+2, +1}}; void dfs(int dep, int x, int y) { //经历了dep个点,现在在(x,y)位置 if (dep == 5 * 5) { //这里dep无法大于5*5,因为dep个已经放了 print(); return; } for (int i = 0; i < 8; i++) { //注意下标从0开始 //跳到的新的坐标 int dx = x + dir[i][0]; int dy = y + dir[i][1]; if (dx <= 0 || dy <= 0 || dx > 5 || dy > 5) continue; //马可能跳出棋盘外 if (vis[dx][dy] != 0) continue; vis[dx][dy] = dep + 1; //是dep+1个放的 dfs(dep + 1, dx, dy); vis[dx][dy] = 0; //撤销 } } int main() { ios; vis[1][1] = 1;//起点是第一个放的 dfs(1, 1, 1); return 0; }
主要过程——dfs大体框架
- 试探节点A;
- A是否满足这个图(或树)中;
- 如果在,标记A如果已经被试探过的话,所影响的各种值;
- 紧接着,去试探所有的A可以达到的节点
- 等等所有的都执行完之后,还原标记A
广度优先搜索(bfs)
一层一层的走!
- 广搜总是每次都把离上一状态最近的状态用一个队列记录下来;
- 记录之后,检查队列是否为空,如果不为空,就将队首元素弹出,并且以这个状态为根节点,进行广度优先搜索;
- 直到整个队列为空为止。
走迷宫(bfs)
设有一个n×m方格的迷宫,即n行m列的迷宫。迷宫格子中分别放有0和1,1表示可通,0表示不能,从某点开始,有上下左右四个方向可走,输入起终点坐标问能否走出去。
手工算法
- 从第一节点开始,逐步计算
- 怎么算?
- 建立一个等待处理的节点的队列。
- 先把一步以内能够走到的节点加进来。
- 然后对这个队列的元素进行处理:即从队列中删除这个节点A,然后再把这个节点A的能够一步走到的节点加入队列。
对于坐标的处理方法
本来是用两个数来表示的坐标(x,y),可以用一个数来表示
;
为什么要这样?简便咯
第 行第 列的格子编号:i*m+j
(m是列数
,横纵坐标的起点都是0
);
反之,编号为u的节点
,其行号
和列号
分别为u/m
,u%m
;
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; typedef long long LL; int n, m; //行,列 int a[10][10]; //存迷宫 int dis[10][10]; //既表示距离也表示是否走过的状态 queue<int> q; //存还没有处理的点 const int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0}; //上下左右四个方向 //判断是否在迷宫里 int inmap(int x, int y) { return x >= 0 && y >= 0 && x < n && y < m; } void bfs(int x, int y) { //因为dis = 0要表示起点到起点的距离,所以这里不能用0和!0表示状态 //所以用-1表示没走过 memset(dis, -1, sizeof(dis)); dis[x][y] = 0; q.push(x * m + y); while (!q.empty()) { //拿出队首 int temp = q.front(); int x = temp / m; int y = temp % m; q.pop(); //别忘记pop掉 for (int i = 0; i < 4; i++) { int dx = x + dir[i][0]; int dy = y + dir[i][1]; //判断是否在迷宫里,是否可以走,是否没走过 if (inmap(dx, dy) && a[dx][dy] == 1 && dis[dx][dy] == -1) { dis[dx][dy] = dis[x][y] + 1; q.push(dx * m + dy); } } } } int main() { ios; //读入 scanf("%d%d", &n, &m); //为了将点坐标用一个数表示,下标从0开始 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { scanf("%d", &a[i][j]); } } bfs(0, 0); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { printf("%4d", dis[i][j]); } printf("\n"); } return 0; }
数独挑战(dfs)
题目描述
玩家需要根据
现在给你一个数独,请你解答出来。每个数独保证有且只有一个解。
输入描述
输入仅一组数据,共 9 行 9 列,表示初始数独(其中 0 表示数独中的空位)。
输出描述
输出共 9 行 9 列,表示数独的解。注意⾏末没有空格。
输入:
5 3 0 0 7 0 0 0 0 6 0 0 1 9 5 0 0 0 0 9 8 0 0 0 0 6 0 8 0 0 0 6 0 0 0 3 4 0 0 8 0 3 0 0 1 7 0 0 0 2 0 0 0 6 0 6 0 0 0 0 2 8 0 0 0 0 4 1 9 0 0 5 0 0 0 0 8 0 0 7 9
输出:
5 3 4 6 7 8 9 1 2 6 7 2 1 9 5 3 4 8 1 9 8 3 4 2 5 6 7 8 5 9 7 6 1 4 2 3 4 2 6 8 5 3 7 9 1 7 1 3 9 2 4 8 5 6 9 6 1 5 3 7 2 8 4 2 8 7 4 1 9 6 3 5 3 4 5 2 8 6 1 7 9
#include<bits/stdc++.h> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; typedef long long LL; int a[20][20]; int h[20][20], l[20][20], x[20][20]; //表示列,行,小方块内是否出现过1-9 //打表,通过坐标判断属于哪一个九宫格小方块 const int xiao[10][10] = {{0,0,0,0,0,0,0,0,0,0}, {0,1,1,1,2,2,2,3,3,3}, {0,1,1,1,2,2,2,3,3,3}, {0,1,1,1,2,2,2,3,3,3}, {0,4,4,4,5,5,5,6,6,6}, {0,4,4,4,5,5,5,6,6,6}, {0,4,4,4,5,5,5,6,6,6}, {0,7,7,7,8,8,8,9,9,9}, {0,7,7,7,8,8,8,9,9,9}, {0,7,7,7,8,8,8,9,9,9}}; //为了降低复杂度,只找需要填数的坐标 struct ty{ int x, y; }kong[90]; int cnt = 0; void write(){ for(int i = 1; i <= 9; i++){ for(int j = 1; j <= 9; j++){ cout << a[i][j] << " "; } cout << endl; } } void dfs(int dep){ if(dep > cnt){ write(); return; } int xx = kong[dep].x; int yy = kong[dep].y; for(int i = 1; i <= 9; i++){ //如果这一行,列,小方块没填,就填上 if(h[xx][i] == 0 && l[yy][i] == 0 && x[xiao[xx][yy]][i] == 0){ a[xx][yy] = i; h[xx][i] = 1; l[yy][i] = 1; x[xiao[xx][yy]][i] = 1; dfs(dep+1); h[xx][i] = 0; l[yy][i] = 0; x[xiao[xx][yy]][i] = 0; } } } int main() { ios; for(int i = 1; i <= 9; i++){ for(int j = 1; j <= 9; j++){ cin >> a[i][j]; //如果为0需要填,就存入kong中 //不需要就标记第i行a[i][j]个所在的行、列、小方块状态标记为1,表示已填 if(a[i][j] == 0){ kong[++cnt].x = i; kong[cnt].y = j; }else{ h[i][a[i][j]] = 1; l[j][a[i][j]] = 1; x[xiao[i][j]][a[i][j]] = 1; } } } dfs(1); return 0; }
幸运数字Ⅱ
题目描述:
定义一个数字为幸运数字当且仅当它的所有数位都是4或者7。
比如说,47、744、4都是幸运数字而5、17、467都不是。
定义
输入描述:
两个整数
输出描述:
一个数字表示答案
八数码
题目描述:
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局,找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!