前言
刚学搜索算法时,在网上看到的大部分文章都是从图的遍历讲起,其实如果没学过图论也也可以了解dfs的一些基本做法。
一、问题引入
现在有n个箱子,有编号为1~n的纸牌,每个箱子只能放一张纸牌,输出将n张纸牌依次放入箱子的每种放法。
怎么办呢?先从比较简单的3张牌说起:
二、解题思路
如下图所示:(第一行是编号,第二行是数值, [ i ] [\ i\ ] [ i ]表示纸牌)
1 | 2 | 3 |
---|---|---|
/ / / | / / / | / / / |
首先,我们将
[
1
]
,
[
2
]
,
[
3
]
[1],[2],[3]
[1],[2],[3]放入箱子,不过我们规定:
每次放牌,都是先放1号,如果1号已经放了,再放2号,以此类推,如图:
- 走到1号箱子前,放入1号纸牌:
1 | 2 | 3 |
---|---|---|
[ 1 ] [1] [1] | / / / | / / / |
- 再走到2号箱子前,按照规则,1号纸牌已经放了,所以放2号:
1 | 2 | 3 |
---|---|---|
[ 1 ] [1] [1] | [ 2 ] [2] [2] | / / / |
- 继续,走到3号箱子前,手中只有3号牌,所以放入3号牌:
1 | 2 | 3 |
---|---|---|
[ 1 ] [1] [1] | [ 2 ] [2] [2] | [ 3 ] [3] [3] |
此时,箱子已经放满了。或者说,把第三个箱子看作是“边界”,再往右走一格,到达假想的第四个箱子前,发现已经超过“边界”——第三个箱子,所以一种放置方法已经完成。
现在我们退回来,取回第三个箱子中的纸牌,此时已经没有超出“边界”了,所以继续往前走,但手中还是只有3号牌,与第3步相同,所以再往后退,取回第2号箱子中的牌,此时,手中已经有两张牌了,于是——
- 在2号箱子前,按照规则,看看能不能放除了2号牌之外的其他牌。按照规则,可以放入3号牌:
1 | 2 | 3 |
---|---|---|
[ 1 ] [1] [1] | [ 3 ] [3] [3] | / / / |
- 同理,3号箱子放入2号牌:
1 | 2 | 3 |
---|---|---|
[ 1 ] [1] [1] | [ 3 ] [3] [3] | [ 2 ] [2] [2] |
又是一种放法。
同理,n张牌也采用同样的处理方式。
好了,说了这么多,看看代码怎么操作——
三、代码实现
- 首先,我们可以用一个数组加上一个for循环来模拟放牌的过程:
for(int i=1;i<=n;i++)
a[step]=i; //将编号为i的牌放入第step个箱子中
- 由于一张牌不能放两次,所以我们用一个book数组来标记第i张牌有没有放过:
for(int i=1;i<=n;i++)
{
if(!book[i]) //第i张牌没放过
{
a[step]=i;
book[i]=true; //此时已经放过了
}
}
- 那么如何处理step+1个箱子呢?其实跟step个盒子的处理方法一样,所以我们可以想到采用递归的方法:
void dfs(int step) //深搜递归函数
{
for(int i=1;i<=n;i++)
{
if(!book[i]) //第i张牌没放过
{
a[step]=i;
book[i]=true; //此时已经放过了
dfs(step+1); //递归处理下一个箱子
book[i]=false; //这部非常关键,相当于将牌收回来,不然继续尝试可就会出问题
}
}
}
- 当然,递归是要有边界的(不然就会陷入死循环了),边界就是到达第n+1个盒子前时停止:
void dfs(int step) //深搜递归函数
{
if(step==n+1) //边界
{
for(int i=1;i<=n;i++) //输出答案
cout<<a[i]<<" ";
cout<<endl;
return; //记得返回,否则递归就无穷无尽了
}
for(int i=1;i<=n;i++)
{
if(!book[i]) //第i张牌没放过
{
a[step]=i;
book[i]=true; //此时已经放过了
dfs(step+1); //递归处理下一个箱子
book[i]=false; //这部非常关键,相当于将牌收回来,不然继续尝试可就会出问题
}
}
}
好了,来看看完整代码:
#include<iostream>
using namespace std;
int a[10],n;
bool book[10]; //标记数组
void dfs(int step) //深搜递归函数
{
if(step==n+1) //边界
{
for(int i=1;i<=n;i++) //输出答案
cout<<a[i]<<" ";
cout<<endl;
return; //记得返回,否则递归就无穷无尽了
}
for(int i=1;i<=n;i++)
{
if(!book[i]) //第i张牌没放过
{
a[step]=i;
book[i]=true; //此时已经放过了
dfs(step+1); //递归处理下一个箱子
book[i]=false; //这部非常关键,相当于将牌收回来,不然继续尝试可就会出问题
}
}
}
int main()
{
cin>>n;
dfs(1); //站在第1个箱子前
return 0;
}
其实,这就是最基础的“生成全排列”问题。