目录
深度优先搜索dfs:
实现数据结构:stack栈
特点:不撞南墙不回头
空间 :O(h)(h表示树的高度)
不具备最短路
例题:排列数字
问题:
给定一个整数n,将数字1~n排成一排,将会有很多种排列方法,请你按照字典序将所有的排列方法输出
输入样例:3
输出样例:
0 1 2
0 2 1
1 0 2
1 2 0
2 0 1
2 1 0
模拟过程:
注意模拟不撞南墙不回头这个思想:
我们从第0层开始向下搜索:即dfs(0),一开始的st[N]数组都是为0的(全局变量不初始化,默认为0),那么我们的第0层就按照for循环的顺序填上path[0]=0,并将0标记为1,表示已经用过了,后面不能再使用了,接下来,我们搜索下一层:即dfs(u+1),此时要注意并不是马上就执行st[i]=0这个操作,而是重新开始执行函数,此时参数为u+1,同样的,还是进入for循环,此时因为已经标记了0,所以0不满足条件,下一个则是1,所以在第一层填上1,至此path已经填好了path[0]=0 path[1]=1 ,接下来重复操作则有path[2]=2
path[2]=2的下一步时dfs(u+1),注意此时u==n,可以执行第一个if语句了,将我们的path数组n大小之前的数输出【0 1 2】并且return回溯
回溯:返回到调用这次递归前的那一层,也就是我们填入path[2]=2的那一层,即第二层:也就是说那一层我们已经执行完了,dfs(u+1)这个操作,按照顺序我们要执行st[i]=0,此时的i很明显是2,相当于我们用了2,在搜索下一层时,我们先标记2不能用,在它搜索回来时,我们将2取消标记
再次for循环:按照for循环的顺序,由于2已经用了,虽然它回来时解除了标记,但是我们已经用不到了,因为i++,导致现在i的值是3,然后我们将3填入path,即此时为path[2]=3,意味着我们的第二层又增加了一个组合:【0,1,3】,相当于碰到底后返回,并且向另外一个分支走去,这就是dfs!
#include<iostream>
using namespace std;
const int N=10;
int n;
int path[N];//存放答案
bool st[N];//标记该元素是否被使用过
void dfs(int u)
{
if(u==n)//当遍历到最后一层时,输出该层
{
for(int i=0;i<n;i++) printf("%d ",path[i]);
printf("\n");
return;//回溯
}
for(int i=0;i<n;i++)
{
if(!st[i])//如果该元素没有被使用过,那么就使用该元素
{
path[u]=i;//存储
st[i]=1;//true标记为已经用过了
dfs(u+1);//向下一层进行搜索
st[i]=0;//回复现场
}
}
}
int main()
{
cin>>n;
dfs(0);
}
例题:n-皇后问题
问题:
n-皇后问题是指将 n 个皇后放在 n∗n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数n,请你输出所有的满足条件的棋子摆法。
输入样例:4
输出样例:
.Q..
...Q
Q...
..Q.
..Q.
Q...
...Q
.Q..
思路解析:
因为每行每列每一斜线都只能放置一个皇后,那么我么就需要三个数组来判断这个位置是否能放置数组:即列数组,正对角线数组,反对角线数组
问题:为什么不是用四个数组来判断?为什么没有行数组?
答:像上题一样,我们是一层层的判断,每一层的一种情况,我们只放置一个数,那么就确保了:每一行只会有一个元素,所以不需要再开一个行数组进行判断
模拟过程:
整体与上面的排列数字一样,多加了一个mp[u][i]='.'的操作,也就是说我回溯回来的时候,证明了这个点的放置是与其他点有冲突的,所以需要重新将已排好的点再次赋值成原始的模样,有一种天然的对称关系^ ^
#include<iostream>
using namespace std;