全排列问题
- 全排列问题
输入数组{1,2,3}的全排列,我们将这个问题形象化。
假设手里有编号1,2,3的三个扑克牌,和1,2,3的三个盒子。需要将三个扑克牌分别放入这个三个盒子当中,并且每个盒子只有一个扑克牌可以放
首先来到一号盒子旁边,每一个都要放一编。约定一个顺序,到一个盒子面前,都先放1号,再放2号,最后放3号。在一号盒子里面放了编号1,来到二号只能放入编号2,3,按照约定只能放入编号2,于是三号盒子只能放入编号3.当到了第四个盒子的时候,发现没有第四个盒子,将前面数字1,2,3记录下来输出,这就是一个全排列。但是没有结束,此时需要回到3号盒子前,将三号盒子的编号3取出,但是手中没有别的编号的牌,所以只能退到二号箱中取出二号箱子的东西。然后手中有2,3两个编号,可以向二号箱子中放入3号牌,然后三号箱子中放入2号牌,此时又产生一个新的排列。
这个就是全排列的思路。首先解决怎么向小盒子中放入扑克牌,这里用一个for循环解决
for(i=1; i <= n; i++){
a[step] = i;//将第i个扑克牌放入第step个箱子中
}
如果一个扑克牌只能放入一个盒子中,那么就将同样的扑克牌放入别的盒子中,因为已经没有这个小盒子了。所以需要用一个数组book来标记哪些牌已经使用过了。
for(i = 1; i <=n; i++){
if(book[i] == 0) //表示扑克牌依旧在手上
{
a[step] = i;
book[i] = 1; //扑克牌已经不在手上了
}
}
处理完第step个·盒子了,接下来还要往前走一步,继续处理第step+1个小盒子,那么如何处理,和处理第step个盒子是一样的,因此将刚才的步骤封装成一个函数
void dfs(int step){ //step表示现在在第几个盒子面前
for(i=1; i <= n; i++){
if(book[i] == 0){ //表示i号扑克牌仍然在手上
a[step]=i; //放入在step盒子中
book[i] = 1; //表示已经放过
}
}
}
处理完第step个盒子,去处理step+1个盒子
void dfs(int step){
for(int i = 1; i <= n; i++){
a[step] = i;
book[i] = 1;
dfs(step+1);
book[i] = 0;
}
}
最后把结束条件加上
void dfs(int step)//step表示现在站在第几个盒子面前
{
int i;
if(step == n+1)//如果站在第n+1个盒子面前,则表示前n个盒子已经放好了扑克牌
{
//输出一种排列
for(i=1;i<=n;i++)
put(a[i]);
return;//返回之前的一步(最近一次调用dfs函数的地方)
}
//站在第step个盒子的面前,按照1、2、3...n的顺序一一尝试
for(i = 1; i <= n; i++)
{
if(book[i] == 0)//表示i号扑克牌仍然在手上
{
a[step]= i;//将i号扑克牌放入到第step个盒子中
book[i] = 1;//表示i号扑克牌已经不在手上了
dfs(step+1);
book[i] = 0;
}
}
}
上面的步骤就是dfs的应用,理解深度优先搜索的关键在于解决“当下该如何做”。至于“下一步如何做”则与“当下该如何做”是一样的。比如我们这里写的dfs(step)函数的主要功能就是解决当你在用step个盒子的时候你该怎么办。通常的方法就是把每一种可能都去尝试一遍(一般用for循环遍历)。当前这一步解决后便进入下一步dfs(step+1)。下一步的解决方法和当前这一步的解决方法是完全一样的。下面的代码就是深度优先搜索的基本模型。
void dfs(int step){
判断边界
尝试每一种可能(){
继续下一步dfs(n+1);
}
}
从n个数中取k个排列的问题
上面已经解决了全排列的问题,就是盒子数和排数相等,现在我们解决非全排列的问题。在这里k是小于等于n的,这个说明每个盒子放上了牌或者没有放上牌。
```void dfs(int step)//step表示现在站在第几个盒子面前
{
int i;
if(step == k+1)//如果站在第k+1个盒子面前,则表示前n个盒子已经放好了扑克牌
{
//输出一种排列
for(i=1;i<=n;i++)
put(a[i]);
return;//返回之前的一步(最近一次调用dfs函数的地方)
}
//站在第step个盒子的面前,按照1、2、3...n的顺序一一尝试
for(i = 1; i <= n; i++)
{
if(book[i] == 0)//表示i号扑克牌仍然在手上
{
a[step]= i;//将i号扑克牌放入到第step个盒子中
book[i] = 1;//表示i号扑克牌已经不在手上了
dfs(step+1);
book[i] = 0;
}
}
}
<div class="se-preview-section-delimiter"></div>
组合问题
组合问题和排列问题最大的区别就是:组合没有顺序,排列有顺序,123 和 132在组合中是同一种组合,但是在排列中确是不同的排列。假设有5个牌需要放到三个盒子中去。假设一号箱子里面已经有了一号牌,二号箱子里面已经有了二号牌,顺序是123,124,125的情况已经全部尝试过了,就不能考虑一号箱子是1号牌而其他箱子有2号牌出现的情况了。如同123和132
1-2-x的所有情况我们都考虑完后,我们就可以在排除2的情况下考虑所有1-3-x的情况。然后是1-4-x,但会出现1-5-x吗?不会啦~因为一共只有5张牌哦~而所有的牌此时都被标记为已用哦,即book为1,所以第三个箱子里是没有可以放的牌的!程序会直接跳过滴,我们就不用担心啦。此时带有1的所有组合我们都考虑完毕啦,于是给它对应的book标记上1。于是我们顺利退回到一号箱子,在一号箱子中放入了2号牌,接下来在不考虑1号的情况下排列出2-x-x的组合。思路已经和上面完全一样啦!我们将会得到234、235和245。得到的最后一个组合就是345了。
void dfs(int step){
int i;
if(step == k+1){ //输出一种组合
for(i = i; i <= k; i++){
put(a[i]);
}
return;//返回最近一次调用dfs的地方
}
//站在第step个盒子的前面,按照1,2,3...n的顺序一一尝试
for(int i = 1; i <= n; i++){
if(book[i] == 0) //表示扑克牌仍然在手上
{
a[step] = b[i]; //第i个扑克牌中吵架
book[i] = 1;//表示i个扑克牌已经不在受伤了
dfs(step+1);
book[i] = 0;//收回手中的牌
if(flag == 1){
book[i] = 1;
flag = 0;
}
}
if(i == n) //表示在某个箱子已经遍历完成了从1到n号的所有扑克牌
flag = 1;
}
return;
}
<div class="se-preview-section-delimiter"></div>
这样就造成了一个问题:比如说在6选4的组合中,得到1234、1235、1236后,3号牌被标记成了1后,就不会再得到1345、1346、1356这三个组合。所以我们需要将部分数字恢复成可用状态。我们用一个for循环消除从当前牌号的下一位到最后一张牌的标记,以便以后再次使用。
void dfs(int step)//step表示现在站在第几个盒子面前
{
int i;
if(step == k+1)//如果站在第k+1个盒子面前,则表示前k个盒子已经放好了扑克牌
{
//输出一种排列
for(i=1;i<=k;i++)//注意这里只输出
puts(a[i]);
return;//返回之前的一步(最近一次调用dfs函数的地方)
}
//站在第step个盒子的面前,按照1、2、3...n的顺序一一尝试
for(i = 1; i <= n; i++)
{
if(book[i] == 0)//表示i号扑克牌仍然在手上
{
a[step]= b[i];//第i个扑克牌放入到第step个盒子中
book[i] = 1;//表示i号扑克牌已经不在手上了
dfs(step+1);//走到下一个小盒子面前
book[i] = 0;//收回盒子中的牌
if(flag == 1)
{
book[i] = 1;
flag = 0;
for(int j=i+1; j<=n; j++)//消除从当前牌号的下一位到最后一张牌的标记,以便以后再次使用
book[j] = 0;
}
}
if(i == n)//表示在第step个箱子上已经遍历完了从1到n号的所有扑克牌
flag = 1;
}
return;
}
int main()
{
for(int i=1; i<=n; i++)
b[i] = i;
dfs(1);//首先站在1号小盒子面前
return 0;
}