首先考虑一道奥数题目:
问题一:
□□□ + □□□ = □□□,要将数字1~9分别填入9个□中,使得等式成立。例如173+286 = 459。请输出所有合理的组合的个数。
我们或许可以枚举每一位上所有的数,然后判断每一位上的数需要互不相等且满足等式即可,但是用代码写出来需要声明9个变量且判断。
那么我们把这个问题考虑为一个求这个9个数的全排列问题,即可得到更优雅的解答方式。
问题二:
输入一个数,输出1~n的全排列。
实例:现在我们考虑有1、2、3的3张扑克牌和编号为1、2、3的3个盒子,需要将这3张扑克牌放到3个盒子里,求其所有可能性。
解析:
2.接着考虑2号盒子,现在我们手里剩下2号和3号扑克牌,于是我们可以把2号扑克牌放入2号盒子中。于是在3号盒子只剩一种可能性,我们继 续把3号扑克放入3号盒子。此时产生了一种排列——{1,2,3}。
3.接着我们收回3号盒子中的3号扑克牌,尝试一种新的可能,此时发现别无他选。于是选择回到2号盒子收回2号扑克。
4.在2号盒子中我们放入3号扑克,于是自然而然的在3号盒子中只能放入2号扑克。此时产生另一种排列——{1,3,2};
5.重复以上步骤就能得到数字{123}的全排列。
1、现在我们用C语言代码描述往每个小盒子中放入所有可能扑克牌的步骤:
for(int i = 1; i <= n; i++){ a[step] = i; //将i号扑克牌放入第step个盒子中 }
2、a是一个装入了所有小盒子的数组,变量step表示当前正处于第step号小盒子。i则表示扑克牌的序号。现在我们需要考虑另外一个问题,则如果一张扑克牌已经被放入别的盒子中,则不能再被放入当前盒子。
因此需要一个book数组标记哪些牌已经被使用。此时我们完善上述代码。
for(int i = 1; i <= n; i++)
{ if(book[i] == 0){ a[step] = i; //将i号扑克牌放入第step个盒子中 book[i] = 1; // 置1表示第i号扑克牌不在手中 } }
现在对于step号盒子已经处理完,那么我们要考虑step+1号盒子。第step+1个的盒子的处理方式与第step个盒子的处理方式完全一样。因此,我们可以对上述操作做一个封装。
void dfs(int step)
{
//step表示当前要处理的盒子
for(int i = 1; i <= n; i++)
{
if(book[i] == 0)
{ a[step] = i;
//将i号扑克牌放入第step个盒子中
book[i] = 1; // 置1表示第i号扑克牌不在手中
} } }
于是我们重新回想文章开头阐述的放置扑克牌的思路:
我们在当前盒子放置完第i个扑克牌之后,便立即处理下一个盒子。于是:
void dfs(int step)
{
//step表示当前要处理的盒子
for(int i = 1; i <= n; i++)
{
if(book[i] == 0)
{
a[step] = i; //将i号扑克牌放入第step个盒子中
book[i] = 1; // 置1表示第i号扑克牌不在手中
dfs(step+1); //递归调用
book[i] = 0;
// 非常重要,收回该盒子中的扑克牌才能进行下一次尝试。
} } }
需要注意到的是,我们需要收回每一次尝试的扑克牌i,才能进行下一次尝试。
现在需要考虑最后一个问题,那就是什么时候得到一个满足要求的排列,也就是考虑终止条件。这里很容易得到,当我们处理完成第n个盒子的时候,就已经得到一个符合要求的排列了。加上终止条件的代码如下:
void dfs(int step){
//step表示当前要处理的盒子
if(step == n+1)
{
//输出排列
for(i = 1; i <= n; i++) printf("%d", a[i]);
printf("\n"); return; }
for(int i = 1; i <= n; i++)
{
if(book[i] == 0)
{ a[step] = i;
//将i号扑克牌放入第step个盒子中
book[i] = 1;
// 置1表示第i号扑克牌不在手中
dfs(step+1); //递归调用
book[i] = 0; // 非常重要,收回该盒子中的扑克牌才能进行下一次尝试。 } } }
现在深度优先搜索(DFS)的基本模型展现在我们眼前。
其核心在于,在当前步骤要把每一种可能性都尝试一遍(使用for循环),解决完当前步骤后进入下一步。而下一步的解决方式完全等同于当前步骤的解决方法。于是可以总结出DFS的基本模型:
void dfs(int step){
*判断结束边界* 尝试每一种可能
for(i = 1; i <= n; i++)
{
尝试下一步 dfs(step + 1);
} return; }