一.暴搜相关概念
暴搜算法(穷举算法)实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就返回,尝试别的路径。
暴搜就是通过不断的搜索将所有情况枚举出来,依次判断,求得最终的答案,因此,暴搜的时间复杂度通常很大,相应的数据范围较小。
暴搜本质还是运用了递归的思想,重复调用自身函数求得答案。
二.相关例题
需要将所有情况考虑的题目,都可以使用暴搜来写(但是耗时,经常TLE超时)
1.子集问题
求出元素为m个的子集合,直接暴搜即可
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n,m;
int a[1001],b[1001];
void dfs(int x,int sum) // x 为当前数组a下标 sum为子集数组b元素个数
{
b[sum]=a[x]; // 加入子集合
if(sum==m) // 如果 子集合元素个数为 m 则直接输出数组b
{
for(int i=1;i<=m;i++) printf("%d ",b[i]);
printf("\n");
return ; // 一定要返回,不然死循环
}
for(int i=x+1;i<=n;i++)
{
dfs(i,sum+1); // 不断向枚举多种情况 将数组a的第i个元素加入到子集合数组b中
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n-m+1;i++) // 从1考虑到n-m+1 因为n-m+1 ~ n 为m个元素,如果仍然往后搜索,一定没有答案
dfs(i,1); // 以数组中第i个元素 为子集中的第一个元素
system("pause");
return 0;
}
2.迷宫问题(八皇后问题)
思路:①先通过暴搜的思想把所有 n 个棋子的位置放好,再考虑这种放置方法是否可行
②判断放置方法是否可行:任意两个棋子不出现在同一条直线上
a.在同一行(列):只需要将所有棋子对应的行列记录下来,出现重复的方法即可直接舍去
b.在同一斜线上: 分为左斜线和右斜线
斜线1:当两个棋子的行列之差相等时,即在同一条斜线1上
斜线2:当两个棋子的行列之和相等时,即在同一条斜线2上
这里图示说明:
//这里采用了暴力枚举的方法,统计合理个数,没有使用回溯
//也可以使用回溯法写
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n;
int ans;
int flag1[101],flag2[101];
int x1[101],y1[101];
void dfs(int x,int y,int sum)
{
x1[sum]=x; y1[sum]=y; //第i个皇后的横坐标为x1[i] 纵坐标为 y1[i]
if(sum==n) // 当放入了 n 个皇后
{
memset(flag1,0,sizeof(flag1)); //重置数组
memset(flag2,0,sizeof(flag2));
for(int i=1;i<=n;i++)
{
if(!flag1[x1[i]]) flag1[x1[i]]=1; //判断是否在同一行
else return ;
if(!flag2[y1[i]]) flag2[y1[i]]=1; // 判断是否在同一列
else return ;
}
for(int i=1;i<=n;i++) // 二维数组判断任意两个是否在同一条斜线上
for(int j=i+1;j<=n;j++)
{
if(x1[i]-y1[i]==x1[j]-y1[j]) return ; //斜线1
if(x1[i]+y1[i]==x1[j]+y1[j]) return ; //斜线2
}
//这里二重循环明显时间复杂度增加,下文对其进行优化
ans++;
return ;
}
for(int j=1;j<=n;j++)
{
dfs(x+1,j,sum+1); //暴力枚举 考虑下一行放皇后的位置
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
dfs(1,i,1); // 放第一个皇后 于 第1行 第i列
printf("%d",ans);
system("pause");
return 0;
}
三.回溯相关概念
回溯是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
与暴搜的区别:在完成某个枚举时返回过程中,将在上一次枚举中修改过的变量返回到原来的状态
四.相关例题
1.全排列问题
题目描述
输入一个数n,输出从 1 到 n 的所有排列情况(按字典序排列)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n,m;
int a[1001],b[1001];
int num[1001];
void dfs(int x,int sum)
{
num[sum]=x;
if(sum==n)
{
for(int i=1;i<=n;i++) printf("%d ",num[i]);
printf("\n");
return ;
}
for(int i=1;i<=n;i++)
{
if(b[i]==1) continue;
b[i]=1;
dfs(i,sum+1);
b[i]=0; // 回溯过程
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
b[i]=1;
dfs(i,1);
b[i]=0; // 回溯
}
system("pause");
return 0;
}
2.迷宫问题(八皇后问题—回溯)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n;
int ans;
int f[101];
int flag1[101],flag2[101],flag3[101];
void print()
{
if(ans<=3)
{
for(int i=1;i<=n;i++)
printf("%d ",f[i]);
printf("\n");
}
return ;
}
void dfs(int x)
{
if(x>n)
{
ans++;
print();
return ;
}
for(int i=1;i<=n;i++)
{ // check函数判断是否能在 x,i 处放置皇后
// flag1 判断列 flag2 判断斜线2 flag3 判断斜线1
if(!flag1[i] && !(flag2[x+i]) && !(flag3[x-i+n])) //优化上文两重循环
{
// 成功放入
f[x]=i;
flag1[i]=1;
flag2[x+i]=1;
flag3[x-i+n]=1;
dfs(x+1);
flag1[i]=0;
flag2[i+x]=0;
flag3[x-i+n]=0;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1);
printf("%d",ans);
system("pause");
return 0;
}