深度优先搜索DFS
从问题某种可能情况出发,搜索所有能到达的可能情况,然后以其中一种可能的情况为新的出发点,继续向下探索,当所有可能情况都探索过且无法到达目标的时候,再回退到上一个出发点,继续探索另一个可能情况,这种不断回头寻找目标的方法称为"回溯法"。
相关概念
- 状态 :状态即事物所表现出来的形态
- 状态空间:所有状态(结点)的集合,如何存储的问题
- 问题状态空间树:是指用树的结构来表示所有的问题状态,树中的每个结点确定了所求解的一个问题状态,树中有些结点代表的是答案状态,即是那些满足求解条件的状态
算法框架-递归回溯
DFS(状态);
{
if(目标状态){处理;}
else for each path do
if(可行){
修改状态;
DFS(新状态);
状态恢复;
}
}
main(){
DFS(初始状态);
}
回溯算法实现关键
- 状态表示
- 目标状态
- 路径列表
- 可行性判定
- 修改状态
- 状态恢复
例题1-全排列
1. 采用搜索路径进行可行性判定
int n,arr[999] = {0}; //全局变量
//状态a数组
//step为当前填第几个数
//将第step个数填入的数组下标为step-1
void DFS(int step){
//目标状态判断 大于n时输出
if(step>n){ //当填入的数的个数大于给定n时,即结束,输出一次结果。
for(int i = 0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
return;
}
//从1到n开始找可行数
for(int i = 1;i<=n;i++){ //枚举路径列表
bool f = true; //可行标记
for(int j = 0;j<step-1;++j) //查看前方数组内是否含有欲找的可行数
//当前欲在step-1处填入第step个数,那么已经填好的数的下标则为从0到step-2 循环判断
if(a[j]==i) //如果已有与该值相同的数被填充
f = false;
//修改状态
if(f){ //如果没有与该值相同的数被填充,则选取该数
a[step-1] = i; //将其放入下标step-1位置
DFS(step+1); //递归求数组内下一位
//状态恢复 进行下一次循环
a[step-1] = 0; //目标状态判定结束且输出后 层层回退使数组值为0
}
}
}
int main(){
cin>>n;
DFS(1);
for(int i = 0;i<n;i++) //这里打印全0
cout<<a[i]<<" ";
return 0;
}
2. 采用数组标记进行可行性判定
int n,a[999] = {0},vh[999] = {0};
void DFS(int step){
//目标状态判断 大于n时输出
if(step>n){
for(int i = 0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
return;
}
for(int i = 1;i<=n;i++){ //枚举路径列表
if(vh[i]==0){ //对应数组vh下标的值为0,则表示可行
a[step-1] = i; //将其放入位置
vh[i] = 1; //修改值为1
DFS(step+1); //递归求数组内下一位
//状态恢复 进行下一次循环
vh[i] = 0; //一次循环后,层层回退,使标记全部置0
//这里不再需要将a[step-1]值0
}
}
}
int main(){
cin>>n;
DFS(1);
for(int i = 0;i<n;i++) //此时打印最后一次结果
cout<<a[i]<<" ";
return 0;
}
例题2-n皇后问题
问题描述:在n×n格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
解决思路:
- 每列只有一个皇后,可以用一维数组来记录每列皇后所在的行,用数组的下标表示所在的列(从1开始),那么它的解一定在全排列的解中
ㅤㅤ- 但多出的约束条件是要判断是否在同一斜线上发生冲突,现假设在第k列第i行上有一皇后,即arr[k] = i,下面将第j列皇后放在第arr[j]行上,则冲突条件为:
arr[j]==i || abs(i-arr[j])==abs(j-k)
,即当第arr[j]行和第i行为同一行或者行差等于列差时,发生冲突。
下面,在全排列基础上对判断条件进行修改:
#include "<bits/stdc++.h>"
using namespace std;
int n,arr[999] = {0}; //全局变量
void DFS(int step){ //step为列号 数组从下标1开始存储
//目标状态判断 大于n时输出
if(step>n){ //当填入的数的个数大于给定n时,即结束,输出一次结果。
for(int i = 1;i<=n;i++)
cout<<arr[i]<<" ";
cout<<endl;
return;
}
//从1到n开始找可行数
for(int i = 1;i<=n;i++){ //枚举路径列表 枚举行数
bool f = true; //可行标记
for(int j = 1;j<step;++j) //查看前方数组内是否含有欲找的可行数
if(arr[j]==i || abs(step-j)==abs(arr[j]-i)) //行号a[j]和前面存在相同或在同一斜线上时
f = false;
//修改状态
if(f){ //如果没有与该值相同的数被填充,则选取该数
arr[step] = i; //将其放入下标step-1位置
DFS(step+1); //递归求数组内下一位
//状态恢复 进行下一次循环
arr[step] = 0; //目标状态判定结束且输出后 层层回退使数组值为0
}
}
}
int main(){
cin>>n;
DFS(1);
for(int i = 1;i<=n;i++) //这里打印全0
cout<<arr[i]<<" ";
return 0;
}
- ——————END-2022-01-07——————
- 个人学习笔记,如有纰漏,敬请指正。
- 感谢您的阅读。