递归方法的使用之一 -- 用递归进行排列组合

本文介绍了如何使用递归方法解决排列和组合问题。对于排列,通过递归选择n个元素中的n-1个进行排列,当序列长度为1时返回。组合问题中,递归地从n个元素中选择m个,利用(n,m) = (n-1,m-1) + (n-1,m)的公式。文中给出了具体的递归函数实现。" 87898228,5792664,特征选择:过滤式、包裹式与嵌入式,"['机器学习', '数据预处理', '特征工程', '模型优化', '特征选择算法']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 排列的问题。
    本文中的排列有一个前提:被排列的对象是不能有重复的。因为在本文的叙述中并没有考虑到重复的情况。请读者自己处理。
    这类问题一般都是这样的:
    有一个序列,不重复,希望得到这个序列的全排列。
    考虑,一个只有两个元素的序列,如AB,只有两个排列,AB,BA。因为AB已经给出,所以我们只要求BA就可以。
    所以对于任意多个元素的序列,我们可以他们分成两个部分,对于第一种已经给出,所以我们只要求第二中排列就可以了。
    但是问题并不是这么简单。对于全排列来讲,每个部分分割的元素不同都会引起排列的变化,因此要首先找出组合,每个组合将产生一类新的排列。因此,排列问题将被这样处理:
    假设序列中有N个元素,选择其中的m个,进行分割
    排列(待排列序列)
    {
                while(选取一个组合 (n,m))
                {
                           while(组合(n,m)中的一个排列)
                           {
                                       排列 (组合(n,m-1))
                            }
                 }
     }
    可以看到,对于这种方法有一个问题:不能同时对(n,m)和(n,m-1)进行递归。因此,我们选择m=n-1,那么选取的组合只有n个了。因此,这样的递归就可以写成
     排列 (待排列序列)
     {
               if (序列长度==1) return;
                for(int i=0;i<序列长度;i++)
                {
                       //选取1~n-1为一个组合,并对该组合排列
                      排列 (组合(n,n-1))
                      选取下一组组合:将第一个元素移植到最后,这样第1~n-1的组合就会发生变化。
                } 
      }
      上面用了一个技巧,就是固定选择第1~n-1个元素为待排列组合,(n,n-1)组合因为只有一个元素,所以只有一种排列。
      通过将第一个元素移动到最后,把原来的第二个元素作为新的第一个元素,这样就在下次时形成一个新的组合。
      当然方法不是唯一的。道理是一样的。
  
    下面我用一个对任意单词序列排列做例子
     功能说明:给出一组不重复单词序列,给出其所有可能的单词序列
 void arrang(char *strLetters,int len)
 {
  if(len<=1)
  {
   printf("%s ",strLetters); //判定在此时遇到一个全排列。在递归调用过程中,
          //strLetters的指针从未发生变化
   return;
  }

  for(int i=0;i<len; ++i)
  {
   arrang(strLetters,len-1);
   //将第一个元素移动到最后
   char ch = strLetters[0];
   memcpy(strLetters,strLetters+1,len-1);
   strLetters[len-1] = ch;
  }
 }

   对于非全排列的问题,选择的个数不是受制于元素的长度,而是受制与指定的个数。
  
     排列 (待选序列,待选个数)
    {
                if(待选个数==0)//无数据可选
                           return;
 
               for(int i=0;i<待选序列长度;++i)
               {
                        选取  第一个元素
                        排列 (其余元素的序列,待选个数-1)
                       
                        将第一个元素移植到最后,把下个元素作为第一个元素,在下次选中           
               }
     }  
  可以看出,如果待选个数==序列的长度的话,就是以全排列。
     
2. 组合的问题
   在上面我们略略的涉及到组合的问题,但进限于与选取n-1个这些显而易见的问题。 下面我们考察一下关于从n个元素中选出m个元素的所有可能的情况。
   众所周知,组合的公式是nX(n-1)X(n-2)....X(m+1)Xm. 当我们从n个中选择1个时,可以选择n种,需要在剩余的n-1中选择m-1个或者m个。
  假设,从n中选择m个组合,可以用(n,m)表示,那么
  (n,m) = (n-1,m-1) + (n-1,m):

  组合 (待选序列,待选个数)

        if(待选个数==0) //已经选完
              return;
        if(待选个数>待选序列的长度) //不符合条件的
             return;
       for(int i=0;i<待选序列长度;++i)   //从0~n,保证每个元素都会被选择到。
       {
               选取 第i个元素                                 //小于i的元素已经被选择过,不能再选择
               组合 (i元素以后的元素,待选个数-1)   // 调用递归,从 n-i,中选择 m-1个组合,
               //注意for的最后是 ++i, 元素自动向后挪动一位,下次循环时,从n-i中选择m个。
       }
 }
 
 这种方法需要事先保存前次选择的结果,才能得到正确的结果。
 下面是一个简单的例子:从已知的字符串中选取固定长度的组合:
 char szBuffer[100];
 int  bufIdx = 0;
 void com(char* strLetter,int len,int select)
 {
  if(len<select)
   return;
  if(select<=0) //选取结束,输出数据
  {
   szBuffer[bufIdx] = 0;
   printf("%s/t",szBuffer);
   return;
  }

  for(int i=0;i<len;++i)
  {
   szBuffer[bufIdx] = strLetter[i]; //选取第i 个元素
   bufIdx ++; //维护缓冲区的正确位置
   com(strLetter+i+1,len-i-1,select-1); //从 剩下的序列中选取 select-1个元素
   bufIdx --;
   //i自动加1,从len-i-1中选取select个元素。
  }
 }
可以看到,我们用了额外的szBuffer来保存选取过程中的状态,因为上面的方法,是从"一个一个"的选取的,从微观入手,
依靠递归来分解选取的过程的。
如果您有更好的方法,欢迎赐教。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值