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来保存选取过程中的状态,因为上面的方法,是从"一个一个"的选取的,从微观入手,
依靠递归来分解选取的过程的。
如果您有更好的方法,欢迎赐教。