做项目遇到数据采集系统中ADC拼合问题,如果顺序不对,波形就是错误的(题外话),为了找到正确的顺序,涉及到排列问题。
一般地,从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(Arrangement)。特别地,当m=n时,这个排列被称作全排列(Permutation)。
Ex:考虑三个数字1,2,3,这个序列有6个可能的排列组合
123
132
213
231
312
321
这些排列组合根据less-than操作符做字典顺序的排序。
字典顺序顾名思义是就是将1-n的一个排列看成一个数,然后按照字典的顺序从小到达的输出
所谓的全排列,就是说将数字进行不重复的排列,所有得到的序列,就是全排列
给定数字1 , 2 , 3 , 4,其全排列是:
{1,2,3,4}, {1,2,4,3}, {1,3,2,4}, {1,3,4,2}, {1,4,2,3}, {1,4,3,2}
{2,1,3,4}, {2,1,4,3}, {2,3,1,4}, {2,3,4,1}, {2,4,1,3}, {2,4,3,1}
{3,1,2,4}, {3,1,4,2}, {3,2,1,4}, {3,2,4,1}, {3,4,1,2}, {3,4,2,1}
{4,1,2,3}, {4,1,3,2}, {4,2,1,3}, {4,2,3,1}, {4,3,1,2}, {4,3,2,1}
全排列如上所示,那么什么是全排列的序号?这里我们通常将全排列按照字典序进行编排,就如上面从左到右看,就是按照字典序排列的。
我们说,对于1,2,3,4的全排列,第20号序列是{4,1,3,2},因为其在这个按照字典序排列的全排列中处在第20的位置。
在STL中,有next_permutation的算法实现。
next_permutation()会取得[first,last) 所标之序列的下一个排列组合。如果没有下一个排列组合,便返回false,否则返回true。
算法:
首先,从最尾端开始往前寻找两个相邻元素,令第一个元素为*i,第二个元素为*ii,且满足*i < *ii。找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个大于*i 的元素,令为*j ,将i,j元素对调,再将ii之后的所有元素颠倒排序。
如下图:方框为i和ii
Code:
class Solution {
public:
void nextPermutation(vector<int>& nums) {
vector<int>::iterator first=nums.begin();
vector<int>::iterator last=nums.end();
if(first==last) //empty
return;
vector<int>::iterator i=first;
i++;
if(i==last) //only one element
return;
i=last; //i指向尾端
i--;
for(;;)
{
vector<int>::iterator ii=i;
--i; //锁定一组(两个)相邻元素
if(*i<*ii) //如果前一个元素小于后一个元素
{
vector<int>::iterator j=last; //j指向尾端
while(!(*i < *--j)); //从尾端往前找,直到比*i大的元素
iter_swap(i,j);
reverse(ii,last);
return;
}
if(i==first)
{
reverse(first,last);
return;
}
}
}
};
首先,从最尾端开始往前寻找两个相邻元素,令第一个元素为*i,第二个元素为*ii,且满足*i > *ii。找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个小于*i 的元素,令为*j ,将i,j元素对调,再将ii之后的所有元素颠倒排序。
代码和next_permutation类似
康托展开式实现了由1到n组成的全排列序列到其编号之间的一种映射
公式:
X=an*(n-1)!+an-1*(n-2)!+…+ai*(i-1)!+…+a2*1!+a1*0!
由1到n这n个数组成的全排列,共n!个,按每个全排列组成的数从小到大进行排列,并对每个序列进行编号(从0开始),并记为X;比如说1到4组成的全排列中,1234对应编号0,1243对应编号1。
对于ai(系数)的解释需要用例子来说明:
对1到4的全排列中,我们来考察3214,则
a4={3在集合(3,2,1,4)中是第几大的元素,有多少个逆序对}=2
a3={2在集合(2,1,4)中是第几大的元素,有多少个逆序对}=1
a2={1在集合(1,4)中是第几大的元素,有多少个逆序对}=0
a1=0(最后只剩下一项)
也就是说康托公式中的每一项依次对应全排列序列中的每个元素,并按上述规则映射;
则X=2*3!+1*2!+0*1!+0*0!=14,即3214对应的编号为14。
Code:
//已知排列求序号
long long getIndex(short dim, short *rankBuf)
{
int i, j;
long long index = 0;
long long k = 1;
for (i = dim - 2; i >= 0; k *= dim - (i--))//每一轮后k=(n-i)!,注意下标从0开始
for (j = i + 1; jif (rankBuf[j]index += k;//是否有逆序,如有,统计,即计算系数
return index;
}
康托公式可以根据排列的序号来求出该排列,即通过X的值求出ai(i大于等于1小于等于n)的值,运用辗转相除的方法即可实现,现在已知一个编号14(注意该编号是从0开始的,如果是从1开始,此处要减1),求其对应的全排列序列:
14 / (3!) = 2 余 2
2 / (2!) = 1 余 0
0 / (1!) = 0 余 0
0 / (0!) = 0 余 0
故得到:a4=2,a3=1,a2=0,a1=0,由ai的定义即可确定14对应的全排列为2103.
Code:
//已知序号求排列
void getPermutation(int dim, short *rankBuf, long long index){
short i, j;
//求逆序数数组
for (i = dim - 1; i >= 0; i--)
{
rankBuf[i] = index % (dim - i);
index /= dim - i;
}
for (i = dim - 1; i; i--)
for (j = i - 1; j >= 0; j--)
if (rankBuf[j] <= rankBuf[i])
rankBuf[i]++; //根据逆序数数组进行调整
}