搜索与回溯算法

基本思想

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

拿迷宫为例

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

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
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值