DFS解决全排列

问题一:不含有重复元素的全排列问题
如:输入一个数n,输出1~n的全排列

假设,现在有编号为1、2、3的三张扑克牌,以及编号为1、2、3的三个盒子,将这三张扑克牌放在三个纸盒子内,求其所有可能性。

解析:

1、首先,我们先考虑1号盒子,我们约定每到一个纸盒子面前都按数字递增的顺序摆放扑克牌。把1号扑克牌放在1号纸盒内,再依次摆放2、3号扑克牌。于是形成了序列{1,2,3}。

2、接着回收3号纸盒内的扑克牌,尝试新一轮的摆放,此时发现别无他选,于是,进一步回收2号纸盒内的扑克牌,最后形成序列{1,3,2}。

3、重复上述步骤便可得到123的全排列。

代码实现:

for ( int i = 1; i <= n; i++ )

    a[step] = i;//将i号扑克牌放在第step个盒子里

a[]表示装有所有小盒子的数组,step表示当前正处于第step号小盒子,i表示扑克牌的序号。

需要注意的是,如果一张扑克牌已被放在另一个盒子内,则不能再放入当前盒子内。

增加一个visit[]数组表示哪些扑克牌已被使用

完善上述代码:

for ( int i = 1;  i <= n; i++ )

{

      if ( visit[i] == 0 )

      {

          a[step]  =  i; //将i号扑克牌放在第step个盒子里

          visit[i]  = 1; //置1表示第i号扑克牌不在手中

       }    

 }  

现在对于step号盒子已处理完,需要考虑step+1号盒子。

由于step+1号盒子与step号盒子处理的方式相同,所以做一个封装即可。

完善代码:

void dfs ( int step )

{

   for ( int i = 1; i <= n; i++ )

   {

      if ( visit[i] ==0 )

      {

          a[step] = i;

          visit[i] = 1;
       }
  }

}
我们回到文章开头所阐述的放置扑克牌的思路,我们在当前纸盒放完第i号扑克牌后,便立即处理下一个盒子

完善代码:

void dfs( int step )  //step表示当前要处理的盒子
{
   for ( int i = 1; i <= n; i++ )
   {
       if(visit[i] == 0)
       {
            a[step] = i; //将i号扑克牌放入第step个盒子中 

            visit[i] = 1; // 置1表示第i号扑克牌不在手中
            dfs(step+1); //递归调用
            visit[i] = 0; // 非常重要,收回该盒子中的扑克牌才能进行下一次的放置
        }
    }
}

需要注意,我们只有回收每次尝试放置的扑克牌,才能进行下一次放置。
现在考虑最后一个问题,终止条件是什么?
当我们处理完第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(visit[i] == 0)
        {
             a[step] = i; //将i号扑克牌放入第step个盒子中
             visit[i] = 1; // 置1表示第i号扑克牌不在手中
             dfs(step+1); //递归调用
             visit[i] = 0; // 非常重要,收回该盒子中的扑克牌才能进行下一次尝试。
         }
        }
      }
}

深度搜索的核心在于,在当前步骤要把每种可能性都尝试一遍(用for循环),解决完当前步骤后进入下一步,而下一步的解决方法等同于上一步。

故DFS基本模型为:

void dfs ( int step)
{
  //判断结束边界,尝试每一种可能
  for (int i = 1; i <= n;i++ )
  {
      dfs(step+1);//尝试下一步
   }
return ;
}

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n;
int a[100], visit[100];
void dfs(int step)
{
    //终止条件
    if(step == n+1)
    {
        for(int j=1;j<=n;j++)
        {
            cout<<a[j]<<"";
        }
        cout<<endl;
        return ;
    }
    
    for(int i=1;i<=n;i++)
    {
        if(visit[i]==0)
        {
            visit[i]=1;
            a[step]=i;
            dfs(step+1);
            visit[i]=0;
        }
    }
}
int main()
{
    while(cin>>n)
    {
        memset(visit,0,sizeof(visit));
        dfs(1);
    }
    return 0;
}

问题二:含有重复元素的全排列问题

解决关键:去重

结果:统计全排列中,排列结果不同的序列个数。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int len, num, visit[100];
char str[100], a[100];
void dfs(int step)
{
    int j;
    if(step == len)
    {
        printf("%s\n",a);
        num++;
        return;
    }
    for(int i = 0;i < len; i++)
    {
        if(visit[i]==0)
        {
            for(j=i+1; j<len; j++)
            {
                if(visit[j] && str[i] == str[j])//这句命令是怎么做到去重的???
                    break;
            }

            if(j == len)
            {
               visit[i] = 1;
               a[step] = str[i];
               dfs(step+1);
               visit[i] = 0;
            }
        }
    }
}
int main()
{
    while(scanf("%s",str)!=EOF)
    {
       len=strlen(str);//字符串的长度
       //先用冒泡对序列进行一下排序,作用是使输出的序列从上往下看有序

       for(int i = 0;i < len;i++)
         for(int j=i+1;j<len;j++)
         {
             if(str[i] > str[j])
             {
                 char tmp = str[i];
                 str[i] = str[j];
                 str[j] = tmp;
             }
         }
      num = 0;
      a[len] = '\0';
      dfs(0);//从第一个字符开始搜索
      cout<<num<<endl;
    }
    return 0;
}

 

待修改下方

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int len, num, visit[100];
char str[100], a[100];
void dfs(int step)
{
    /*终止条件,先输出符合条件的序列,再统计个数*/

    if(step == len)
    {
        printf("%s\n",a);
        num++;
        return;
    }

    for(int i = 0;i < len; i++)
    {
        if(visit[i]==0)
        {
            int j;
            /*去重*/
            for(j=i+1; j<len; j++)
            {
                if(visit[j] && str[i] == str[j])//这个字符已被使用,且重复,便跳过。
                                                /*这里的for循环 因为用冒泡对序列排了序,这个字符若有多个,第一次使用时不会与前方重复,
                                                但用第二个时便与前方的重复了便会与前方的字符相同     想法错
                                                这里的for循环,是从第i字符开始,后边的字符逐个与它比较,只要有与它相同的字符,便跳过,
                                                这样下方的if(j == len)条件便不会成立
                                                */
                    break;
            }
            //
            if(j == len)//注意这里的判断条件,只有当j==len时这个数才会被存入,根据上方的判断
            {
               visit[i] = 1;
               a[step] = str[i];
               dfs(step+1);
               visit[i] = 0;
            }
        }
     }
}
int main()
{
    while(scanf("%s",str)!=EOF)
    {
       len=strlen(str);//字符串的长度
       //先用冒泡对序列进行一下排序,作用是使输出的序列从上往下看有序
       for(int i = 0;i < len;i++)
         for(int j=i+1;j<len;j++)
         {
             if(str[i] > str[j])
             {
                 char tmp = str[i];
                 str[i] = str[j];
                 str[j] = tmp;
             }
         }
      num = 0;
      a[len] = '\0';
      dfs(0);//从第一个字符开始搜索
      cout<<num<<endl;
    }
    return 0;
}


 

 

 


 

 

 

    

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值