基本思想
搜索与回溯算法基本思想是,当我们遇到多种方案的时候,我们可以先选择其中一种方案,如果方案不对,那返回去继续搜索。
拿迷宫为例
我们需要去做的是从蓝色走到红色,如果使用搜索与回溯算法的话,
当到其中一个节点的时候,先选择一个节点走,
这时候我们发现已经没路了,然后回到上次选择点,同样道理,我们发现到上一个选择点的时候又没路了,最后回到前面的一个选择点,以此类推,我们就可以走到我们的终点去了,然后我们就走到终点去了。
如何用代码去实现算法?
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函数中输出最终解的数量。
对于搜索回溯最重要的就是将问题划分为多个重复阶段,一旦发现某个解是错误的,就回溯到上一个节点,再次进行查找
如有差错,还请个位指教!!!!!!!!!!!