不知大家有没有见过这两个函数——num2perm以及perm2num
稍后我详细解释
为什么要写这篇博客呢,因为我觉得在搜寻答案的过程中,查看网上的解读没有哪个是写得让我比较满意的,对于全排列及其序号之间的换算,我希望能在这篇博客中说清楚。
首先补充基础知识——什么是全排列及其序号?(了解的同学可以直接跳过下面一段)
所谓的全排列,就是说将数字进行不重复的排列,所有得到的序列,就是全排列
给定数字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作为例子
全排列如上所示,那么什么是全排列的序号?这里我们通常将全排列按照字典序进行编排,就如上面从左到右看,就是按照字典序排列的。
我们说,对于1,2,3,4的全排列,第20号序列是{4,1,3,2},因为其在这个按照字典序排列的全排列中处在第20的位置。
——————铺垫完了——————
现在要讨论的问题是什么呢?如下:
1 给定一个排列,求这个排列的序号是多少?
2 给定一个排列的序号以及排列中数字的个数,那么这个排列是什么?
用实例来说,就是:
1 问{4,1,3,2}的序号是多少?
2 问有4个数字1,2,3,4的全排列中,第20个排列是什么?
下面将给出较为高效的计算方法,引用自:第六章_组合数学中的程序设计.ppt
——————已知排列求序号——————
正常来讲,大家肯定能够想到暴力搜索,就是使用一个递归,让最低位(最右边)的数字每次尝试增加1,如果超过数字上限,那么就改为由其高一位增加1,这样慢慢直到对应到当前的排列,同时找到序号。
另外一个方法是使用排列组合,对于{4,1,3,2},先看最高位4,在{4,?,?,?}的排列前面,起码肯定有{3,x,y,z},{2,x,y,z},以及{1,x,y,z},其中x,y,z表示任何数,这样的排列一共有3*3! 种(如果不知道为什么的同学,可以先看看最基本的排列组合),这个时候再来看次高位1,很显然1在这些数字中是最少的数字,对于{4,1,?,?},其前面不会有{4,1-1,x,y},{4,1-2,x,y}等情况,所以此时更小的排列暂时找不到,有0种,然后再来看3,同样,对于{4,1,3,?},这个排列的前面肯定有{4,1,2,x},不会有{4,1,1,x},因为1已经在前面使用过了,所以这个时候只有1种,最后就是{4,1,3,2},在所有数字已经确定的情况,不会有其它在它前面的数列
于是我们知道排列{4,1,3,2}的前面有3*3!+1=19种排列,那么它自己就是第20号的排列。
有个函数就是这样实现的,函数名为perm2num,如下:
int perm2num(int n, int *p){
int i, j, num=0,k=1;
for (i=n-2;i>=0;k*=n-(i--))//每一轮后k=(n-i)!,注意下标从0开始
for (j=i+1;j<n;j++)
if (p[j]<p[i]) num+=k;//是否有逆序,如有,统计
return num;
}
给定元素个数n,以及数组p,返回全排列的序号,需要特别注意的是,这里返回的序号是以0作为基准的(即假设第一个排列的序号为0),而我们刚才说的是以1为基准的,所以这里传入{4,1,3,2},返回的结果是19,各位根据需要灵活运用即可。理解为主。
对了····理解,怎么理解这个函数?这里把计算过程进一步抽象为寻找逆序对的过程,如下:
排列: 4 1 3 2
逆序数:3 0 1 0
4与其右边的数比较,有3个小于它,所以在4的位置上逆序对有3对;
3与其右边的数比较,有1个小于它,所以在3的位置上逆序对有1对;
那么在{4,1,3,2}之前的排列数为:3*3!+1*1!=19,{4,1,3,2}本身就是第20号了,和上面是一样的,为什么可以这样算?因为逆序对的数目就说明这个位置上的数比当前剩下的多少个数要大,那么很明显又回到上面的{3,x,y,z},{2,x,y,z}以及{1,x,y,z}上面了,计算方法是一样的。
已知排列求序号就讲完了
下面这个难理解一点,要自己动一下脑筋
——————已知序号求排列——————
已知序号怎么求排列?
这里有一个方法,各位可以先参考一下:点击打开链接
先上代码:
//给定元素个数n,排列序号num
//返回对应的排列p
void num2perm(int n, int *p,int num){
int i,j; //求逆序数数组
for(i=n-1;i>=0;i--)p[i]=num%(n-i),num/=n-i;
for (i=n-1;i;i--)
for (j=i-1;j>=0;j--)
if (p[j]<=p[i]) p[i]++; //根据逆序数数组进行调整
}
很巧,但是看懂后就感觉原来也就是这么一回事。我是问了周生才恍然大悟的,哎
第一个循环是计算第i个位置下的数字是剩余数字中(从小到大排序)的第几个,而第二个+第三个循环就是计算具体位置上的数字的,举例如下:
例如对于19(下标从0开始),p[]={3,0,1,0},那么一开始位置0(最左边)可以使用4个数字为1,2,3,4,第3个就是4(下标从0开始);然后对于位置1,剩下的3个数字是1,2,3,第0个就是1;对于位置2,剩下的2个数字是2,3,第1个就是3;最后对于位置3,剩下的数字是2,第0个就是2 。因此就推出原排列是{4,1,3,2}
希望这篇博客能够帮到大家吧
******<转载说明>******
转载注明:诚实的偷包贼
原文地址:http://blog.csdn.net/fanfank/article/details/8973532
******<转载说明/>******