全排列的递归与非递归
假如要求N个字符的全排列,N个字符的集合为S={C1, C2,...,Cn}
1.对于递归的情况,令去掉第i个字符后的集合为Si=S-{Ci},那么集合S的全排列
FullPermutation(S)为:
C1+FullPermutation(S1)
C2+FullPermutation(S2)
...
Cn+FullPermutation(Sn)
加号"+"表示字符连接,显然递归的结束条件是集合中只有一个字符的时候;
{
for(int i = 0;i<l;i++)
{
if(str[i] != 0)
{
buf[s] = str[i];//选入
char t = str[i] ;
str[i] = 0;
permutation(str,l,s+1,buf);
str[i] = t;
}
}
if(s == l)
{
buf[s] = '\0';
//cout<<buf<<endl;
}
}
void main()
{
char y[]= {'a','b','c','d','r','g','e','x','y'};
char buf[128];
permutation(y,9,0,buf);
}
2.对于非递归,有不少的等价方式,其中之一想法如下(摘自http://llfclz.itpub.net/post/1160/278490):
取第一个字符;
取第二个字符,可以放在第一个字符的左边或者右边,记为0和1;
取第三个字符,可以放在最左边,中间和最右边,记为0,1和2;
以此类推,第k个字符有k个位置可选
类似二进位, 八进位那些数系转换关系。可以设计这样一个数, ...xyz, 其中个位数 z 是二进位的, 也就是放第二个数的两个位置; 十位数 y 是三进位的, 代表放第三个数字的三个位子, 然后百位数是四进位, 千位数是五进位的, 依以类推." 没错, 这样设计的话, 如果 0 表示放於最左面的话, 则 "2021" 这个数就代表了排列五个元素 (abcde), 取一个 a, 然后第二个 b 放在 a 的右面成 ab,取 c 放到最右面成为 abc, 取 d 放到最左面成 dabc; 最后 e 放到中间去成为 daebc. 至於 "2021" 这个特别的设计的数可以用2*5+ 0*4 + 2*3 + 1*2 这样的计算来映对到自然数的数列上去。
如求 4 个数的 4! = 24 个排列, 第 18 个排列可以这样求得, 18 除 2, 余数是 0, 所以第二个数放在第一个数的左面; 然后商 9 再除 3, 余数 0, 所以第三个数於在头两个数的最左; 最后 3 除以 4, 余数是 3, 因此第四个数要放在前三个数的第 4 个空位, 也就是最右面。
以下是非递归的代码(留着以后再用...)
//全排列
void FullPermutation(const char *array, int size)
{
int i = 1;
int n = 1;
char *pBuf = new char[size+1];
for (; i <= size; i++)
{//算size的阶乘
n *= i;
}
for (i = 0; i < n; i++)
{
print(i, array, size, pBuf);
}
delete []pBuf;
}
//打印当前idx对应的序列
//为了避免内存new过来delete过去的,就把缓存当成参数了....
void print(int idx, const char *array, int size, char *&pBuf)
{
int i, j, k;
pBuf[0] = array[0];
for (i = 2; i <= size; i++)
{
k = idx%i;
for (j = i-1; j > k; j--)
{//为第i个字符腾出空来
pBuf[j] = pBuf[j-1];
}
pBuf[k] = array[i-1];
idx /= i;
}
pBuf[size] = '\0';
cout << pBuf << endl;
}