搜索与搜索剪枝

搜索

  • 通过不停的试探去寻找解的一种算法
  • 与其说是一种算法,不如说是一种方法
  • 基础的方法有暴力的搜索法,深搜,广搜三种
  • 更高级的有IDDFS(迭代加深搜索),DBFS(双向搜索),A* ,IDA*等等

深度优先搜索(dfs)

(一条道走到黑,走不了了再倒回去)
算法过程:
void dfs(状态A)

  1. 判断当前的状态是否合法。合法则继续执行,否则则回到上次调用。
  2. 先下走一层,也就是调用dfs(状态A+Δ);
    image

输出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。
输出格式
每个解决方案占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)值是一定的!!!(由于主对角线会出现负数,所以可以加一个偏移量列数)
所以就可以用((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表示不能,从某点开始,有上下左右四个方向可走,输入起终点坐标问能否走出去。
image

手工算法

  • 从第一节点开始,逐步计算
  • 怎么算?
  • 建立一个等待处理的节点的队列。
  • 先把一步以内能够走到的节点加进来。
  • 然后对这个队列的元素进行处理:即从队列中删除这个节点A,然后再把这个节点A的能够一步走到的节点加入队列。
    对于坐标的处理方法
    本来是用两个数来表示的坐标(x,y),可以用一个数来表示
    为什么要这样?简便咯
    (i)行第(j)列的格子编号: i*m+j(m是列数,横纵坐标的起点都是0);
    反之,编号为u的节点,其行号列号分别为u/mu%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) 盘面上的已知数字,推理出所有剩余位置的数字,并满足每一行、每一列、每一个粗线九宫格内的数字包含有 (1-9) 的数字,且不重复。
现在给你一个数独,请你解答出来。每个数独保证有且只有一个解。
image
输入描述
输入仅一组数据,共 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都不是。
定义(next(x))为大于等于(x)的第一个幸运数字。给定(l,r),请求出(next(l) + next(l + 1) + ... + next(r - 1) + next(r))
输入描述:
两个整数(l和r (1 <= l <= r <= 1000,000,000)。)
输出描述:
一个数字表示答案

八数码

题目描述:
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局,找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

原文链接: https://www.cnblogs.com/csai-H/p/17051532.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    搜索与搜索剪枝

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/312110

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月16日 下午12:16
下一篇 2023年2月16日 下午12:17

相关推荐