问题描述:n个元素{1,2,3...n}有n!种不同的排列。将这n!个排列按字典序排列,并编号0,1,2,3...n!-1。每个排列的编号为其字典序值。例如,当n=3时,有6个不同排列的字典序如下:
字典序值 | 0 | 1 | 2 | 3 | 4 | 5 |
排列 | 123 | 132 | 213 | 231 | 312 | 321 |
问题分析:比较简单的想法就是就是将n个元素进行排列有n!中组合,然后将这些数进行排序。但是这样存在,效率低下、存储空间消耗巨大的问题。如果运用递归和分治策略来解决问题就会简单的多。
具体步骤:为了求排列对应的序号,只要该序列到第一个序列即0,1,2,3…n-1所需要移动的次数。移动原则是a[i]从大到小移动,对于每一个数字a[i],若i前面比a[i]小的个数正好是a[i]-1个,则这个数不需要向后移动以达到目标序列,否则i后面必然有a[i]-b[i]-1个比a[i]小的数,只要把a[i]移动到比自己小的数后面才能使得移动中的序列正向目标前进。因此只要求出每个数的移动次数,然后相加就是该序列的位置。即每个数到正确位置需要移动的次数为(n-i-1)!*(a[i]-b[i]-1)。
程序代码:
#include<stdio.h>
#include<stdlib.h>
void SWAP(int * a, int * b)
{
long long t;
t = *a;
*a = *b;
*b = t;
}
int main()
{
int n,i,k,j,t,order[100];
int lis,f[100],mid,h;
f[0]=1;
for(i=1;i<=22;i++)
f[i]=f[i-1]*i;
printf("请输入元素的个数,以及一个序列\n");
if(scanf("%d",&n)!=EOF)
{
for(i=0;i<n;i++)
scanf("%d",&order[i]);
if(n==1)
printf("0\n1\n");
else if(n>=2)
{
lis=0;
for(i=0,k=n-1;i<n-1;i++,k--)
{
t=0;
for(j=0;j<i;j++)
if(order[j]<order[i])
t++;
lis+=(order[i]-1-t)*f[k];
}
printf("该序列是第%d个\n",lis);
for(i = n-2; i >= 0; i--)
{
if(order[i] < order[i+1])
{
j = i;
for(k = n-1; k > j; k--)
{
if(order[k] > order[j])
{
mid = j+(n-j)/2;
SWAP(&order[j], &order[k]);
for(j++, h = 1; j <= mid; j++, h++)
SWAP(&order[j], &order[n-h]);
}
}
break;
}
}
printf("该序列的下一个序列是:\n");
for(i=0; i < n-1; i++)
printf("%d ",order[i]);
printf("%d\n",order[i]);
}
}
return 0;
}
运行截图:
总结:
在排列的字典序问题上,在求某个序列是字典序的第几个时,要掌握规律,即(n-i-1)!*(a[i]-b[i]-1)。某个序列移动到最小序列即1234…n时所移动的次数即该序列所在位置,而移动次数又可以转换为每个位置上的数移动到正确位置的次数的和。由此即可求出某个序列在字典序的什么位置。在求解序列的下一个序列时,其核心思想是从数组尾部开始找相邻两个元素,满足order[i]<order[i+1],再从数组尾部开始找第一个大于order[i]的数order[k](k>i),交换order[i]和order[k],order[i+1]~order[n-1]进行逆向重排。