排列生成算法有三种:序数法、字典序法、换位法。本文只讨论前两种较常用的方法。
一.序数法
所谓序数,指的是某个排列在这n的数的所有排列中按字典序排序的序数。已知一个排列,可以用康托展开求其序数。假设排列为,这个排列对应的序数为
。其中
表示位于第i位右边比
小的元素的个数,
排列组合意义上理解:设当前排列,考察第
位
,第
位比
小的排列的
一定比
小,第
位有
中情况,后面的
位的情况为
,所以在
位比排列
小的排列有
种情况。对每一位都进行考虑得到的就是以上的公式。代码如下:(数组的下标从
,与定义中相反,处理时换成
即可)
long cantor(int s[], int n){
int res = 0;
for(int i = 1; i < n; ++i){
for(int j = i + 1; j <= n; ++j)
if(s[j] < s[i])
++cnt;
res += fac[n - i] * cnt; //fac为预处理阶乘
}
return res + 1;
}
逆展开:已知排列序数,求排列。
设排列序数为,则由原公式可得:
,
,以此类推求解到
.
应用:康托展开可以用于状态压缩,使用序数来表示当前状态所构成的排列),大大节约空间。
二.字典序法
字典序法用于求解当前排列的下一个排列。假设当前排列为,步骤如下:
1.求满足的
的最大值,设为
,即:
,则
之后是一个降序序列。
2.求满足的
的最大值,设为
,即:
,则
为大于
的最小值
3.互换,将
中的元素逆转。即得到下一个排列。
上述过程简单说来,就是寻找一个降序结构(在尾部),记住这个子序列之前的那个元素的值,然后在子序列中找最后一个比
大的值
(
是大于
的最小值),交换
和
,整个子序列仍然是降序的,再将这个降序结构逆转重排。
证明如下:
设当前排列,按上述方法得到的下一个排列为
。按康托展开求解这两个排列的序数。
由于第位以前二者相同,所以只考虑
位,为简单起见,把
当成第一位,设长度为
。则根据康托展开的公式,
,由于
,可得:
,化简得:
又:,所以
,即:
为
的下一个排列。证明完毕。
三.STL next-permutaion
STL中的next-permutaion算法采用上面的字典序法生成下一个排列,代码如下:
template<class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last){
if(first == last)
return false;
BidirectionalIterator i = first;
++i;
if(i == last) //只有一个元素
return false;
i = last;
--i; //最后一个元素
for(;;){
BidirectionalIterator ii = i;
--i;
if(*i < *ii){ //找到最后一组相邻元素:a[i] < a[i + 1],a[ii]起降序排列
BidirectionalIterator j = last;
while(!(*i < *(--j)); //找最后一个大于a[i]的元素,则a[j]为大于a[i]中的最小数
iter_swap(i, j); //交换i, j
reverse(ii, last); //将ii后面的元素逆向重排
return true;
}
if(i == first){ //整个区间内元素是降序排列的
reverse(first, last); //全部逆向重排
return false; //当前排列已经是最大排列
}
}
}
pre-permutaion则是按照字典序法相反地进行。如下图所示:
源码如下:
template<class BidirectionalIterator>
bool pre_permutation(BidirectionalIterator first, BidirectionalIterator last){
if(first == last)
return false;
BidirectionalIterator i = first;
++i;
if(i == last) //只有一个元素
return false;
i = last;
--i; //最后一个元素
for(;;){
BidirectionalIterator ii = i;
--i;
if(*i > *ii){ //找到最后一组相邻元素:a[i] > a[i + 1]
BidirectionalIterator j = last;
while(!(*--j < *i)); //找最后一个小于a[i]的元素,则a[j]为小于a[i]中的最大数
iter_swap(i, j); //交换i, j
reverse(ii, last); //将ii后面的元素逆向重排
return true;
}
if(i == first){ //整个区间内元素是降序排列的
reverse(first, last); //全部逆向重排
return false; //当前排列已经是最大排列
}
}
}