搜索与回溯算法

基本思想

搜索与回溯算法基本思想是,当我们遇到多种方案的时候,我们可以先选择其中一种方案,如果方案不对,那返回去继续搜索。

拿迷宫为例

我们需要去做的是从蓝色走到红色,如果使用搜索与回溯算法的话,
在这里插入图片描述
当到其中一个节点的时候,先选择一个节点走,在这里插入图片描述
这时候我们发现已经没路了,然后回到上次选择点,同样道理,我们发现到上一个选择点的时候又没路了,最后回到前面的一个选择点,以此类推,我们就可以走到我们的终点去了,然后我们就走到终点去了。
如何用代码去实现算法?

void dfs(int x) {
	if (到目的地)//意思是判断我们是否到达终点,找到答案
	{
		输出解;
		return;//说明如果我们没有这个return,很容易就会死循环,
	}
	for (i = 1; i <= 方案数; i++)
	{
		if (方案可行)//为什么要加这个语句呢?拿刚才迷宫为例,我可以往左右走,但如果左边是一堵墙,我们还可以往左走吗?
		{
			保存路径//标记一下我走了这条路
			dfs(x+1)//继续往下走,值到遇到岔路口,继续实现函数递归
			恢复保存前状态;
		}
	}
}

例题1 全排列问题

在这里插入图片描述

套用刚才的模板去编写代码
发现题目的样例输出,发现有很多组,每组都是一个数开头,那换言之,每一个岔路口就是1到n
我们取到第二个数的时候,i又会从1开始尝试,为了避免这个情况,我们可以创建一个vis[100]数组,他是默认为0,每次使用它,把它改为1,vis[]改为1,下一次我们先判断vis[i]是否等于0,这样就能很好的避免重复数的出现
我们还要把这个数存在一个数组里面
存进去之后,我们就进去下一个岔路口,我们就进入下一个数 ,然后层层搜索。

#include<iostream>//P1706
#include<cstdio>
#include<cstring>
#include<string>
#include<iomanip>
#include <algorithm>
#include<cmath>
#include<vector>
#include<set>
using namespace std;
int a[10], vis[10];
int n;
void dfs(int x)
{
	if (x > n)//找到终点
	{
		for (int i = 1; i <= n; i++)
		{
			cout << a[i];//输出
		}
		cout << "\n";
		return;
	}
	for (int i = 1; i <= n; i++)//一共有n中可执行的方案
	{
		if (vis[i] == 0)//判断当前方案的可行性
		{
			a[x] = i;//记录当前使用的数
			vis[i] = 1;//标记当前数已被使用
			dfs(x + 1);
			vis[i] = 0;//回溯
		}
	}
}
int main()
{
	cin >> n;
	dfs(1);
	return 0;
}

例题2 迷宫问题

在这里插入图片描述

#include<iostream>//P1706
#include<cstdio>
#include<cstring>
#include<string>
#include<iomanip>
#include <algorithm>
#include<cmath>
#include<vector>
#include<set>
using namespace std;
int n;
int a[10][10];
int ans;
int dx[8] = { -1,-1,0,1,1,1,0,-1 };
int dy[8] = { 0,1,1,1,0,-1,-1,-1 };//预设方向
void dfs(int x, int y)
{
	if (x==n&&y==n)
	{
		ans++;
		return;
	}
	for (int i = 1; i <= 8; i++)
	{
		int xx = x + dx[i];
		int yy = y + dy[i];//存储前行后的坐标
		if (xx >= 1 && xx <= n && yy >= 1 && yy <= n && a[xx][yy] == 0)//判断坐标是否在范围之内
		{
			a[x][y] = 1;
			dfs(xx, yy);
			a[xx][yy] = 0;
		}
	}
}
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			cin >> a[i][j];
	a[1][1] = 1;//标记起点为走过的路
	dfs(1, 1);
	cout << ans;
	return 0;
}

搜索与回溯算法步骤

1 把题目划分为多个重复的操作
2 确定每个操作的可执行方案
3 判断每个方案的可行性

例3 自然数的拆分问题

在这里插入图片描述

#include<string>
#include<iomanip>
#include <algorithm>
#include<cmath>
#include<vector>
#include<set>
using namespace std;
int n;
int a[1000];
void fun(int x, int y)
{
	if (y == 0)
	{
		for (int i = 1; i < x - 1; i++)
		{
			cout << a[i] << "+";
		}
		cout << a[x - 1]<<"\n";
		return;
	}
	for (int i = a[x - 1]; i <= y; i++)
	{
		a[x] = i;
		if (i != n)
		{
			fun(x + 1, y - i);
		}
	}
}
int main()
{
	cin >> n;
	a[0] = 1;
	fun(1, n);
	return 0;
}

例题四

在这里插入图片描述

#include<iostream>//P1706
#include<cstdio>
#include<cstring>
#include<string>
#include<iomanip>
#include <algorithm>
#include<cmath>
#include<vector>
#include<set>
using namespace std;
int a[10000], vis[10000], ans[10000];
int n, m, mar, sum;
void fun(int x, int y)
{
	if(y==m)
	{
		mar = 1;
		for (int i = 1; i < x; i++)
		{
			cout << ans[i];
		}
	}
		for (int i = 1; i <= n; i++)
		{
			if (vis[i] == 0 && mar == 0 && y + a[i] <= m)
			{
				vis[i] = 1;
				ans[x] = a[i];
				fun(x + 1, y + a[i]);
				vis[i] = 0;
			}
		}
	
}
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		sum += a[i];
	}
	if (sum < m)
	{
		cout << "no solution";
		return 0;
	}
	fun(1, 0);
	if (mar == 0)
		printf("asdasd");
}

八皇后问题

在这里插入图片描述

#include<cstdio>
#include<iostream>
using namespace std;
int ans[14], check[3][28] = { 0 }, sum = 0, n;
void eq(int line)
{
    if (line > n)
    {
        sum++;
        if (sum > 3) return;
        else
        {
            for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
            printf("\n");
            return;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        if ((!check[0][i]) && (!check[1][line + i]) && (!check[2][line - i + n]))
        {
            ans[line] = i;
            check[0][i] = 1; check[1][line + i] = 1; check[2][line - i + n] = 1;
            eq(line + 1);
            check[0][i] = 0; check[1][line + i] = 0; check[2][line - i + n] = 0;
        }
    }
}
int main()
{
    scanf_s("%d", &n);
    eq(1);
    printf_s("%d", sum);
    return 0;
}

代码中,

数组ans[line]=i表示第line行的第i列有一个棋子,保证了每行只有一个棋子;

数组check保证了每列和每条对角线上只有一个棋子,具体机制如下,没有一些奇奇怪怪难以理解的公式:

check[0]储存了棋子的列数,每一次进行ans[line]=i,使check[0][i]标记为已使用;

check[1]和check[2]储存对角线上的棋子分布情况

对于一条从右上到左下的对角线,其上的棋子坐标应满足x+y为一定值;

对于一条从左上到右下的对角线,其上的棋子坐标应满足x-y为一定值,为了避免负数的产生,代码中用x-y+n来储存数字,具体效果读者可以自行研究。

对于语句

if((!check[0][i])&&(!check[1][line+i])&&(!check[2][line-i+n]))

只要满足这三个数字均为使用过,则在ans[line]=i处放置棋子,并将check数组中的相应数值标记为已使用,并对下一行进行搜索。

由于题目要求输出前3组解,所以计数器sum>3时不输出结果,最后在main函数中输出最终解的数量。

对于搜索回溯最重要的就是将问题划分为多个重复阶段,一旦发现某个解是错误的,就回溯到上一个节点,再次进行查找

如有差错,还请个位指教!!!!!!!!!!!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
马走日问题是一个经典的搜索回溯算法问题,其目的是求出马从棋盘的某一位置出发,恰好行走k步,经过所有棋盘格子的路径数目。 C++实现马走日问题的搜索回溯算法步骤如下: 1. 首先定义棋盘的大小和马的起始位置。 2. 定义一个二维数组visited来记录每个位置是否被走过。 3. 定义一个数组dx和dy来记录马在x和y方向上的可行移动距离。 4. 定义一个计数器count来记录走过的路径数。 5. 定义一个递归函数dfs,其中参数x和y表示当前马所在的位置,step表示已经走过的步数。 6. 在dfs函数中,首先检查当前位置是否越界或者已经走过,如果是,则直接返回。 7. 如果当前已经走过了k步,则count加1,表示找到了一条符合要求的路径。 8. 否则,从当前位置开始尝试所有可行的移动方式,即往上下左右和斜向上下左右八个方向移动。 9. 对于每个可行的移动,将当前位置标记为已经走过,并递归调用dfs函数。 10. 在递归调用结束后,将当前位置标记为未走过,以便进行下一次搜索。 C++代码实现如下: ```c++ #include <iostream> #include <cstring> using namespace std; const int N = 10; int n, m, k; int cnt; int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2}; int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1}; bool visited[N][N]; void dfs(int x, int y, int step) { if (step == k) { cnt++; return; } for (int i = 0; i < 8; i++) { int nx = x + dx[i]; int ny = y + dy[i]; if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny]) { visited[nx][ny] = true; dfs(nx, ny, step + 1); visited[nx][ny] = false; } } } int main() { cin >> n >> m >> k; int x, y; cin >> x >> y; visited[x][y] = true; dfs(x, y, 0); cout << cnt << endl; return 0; } ``` 这里我们使用了搜索回溯算法的思想,在递归过程中不断尝试所有可能的移动方式,直到找到一条符合要求的路径。同时,我们使用了一个visited数组来记录每个位置是否已经被走过,以避免重复搜索

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值