排列生成算法有三种:序数法、字典序法、换位法。本文只讨论前两种较常用的方法。
所谓序数,指的是某个排列在这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算法采用上面的字典序法生成下一个排列,代码如下:
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; //当前排列已经是最大排列 } } }