排列的字典序问题

Problem Description
n个元素{1,2,……, n }有n!个不同的排列。将这n!个排列按字典序排列,并编号为0,1,…,n!-1。每个排列的编号为其字典序值。例如,当n=3时,6 个不同排列的字典序值如下:


给定n以及n个元素{1,2,……, n }的一个排列,计算出这个排列的字典序值,以及按字典序排列的下一个排列。
Input
输入数据的第1行是元素个数n(n≤20)。接下来的1行是n个元素{1,2,……, n }的一个排列。
Output
输出数据的第1行是字典序值,第2行是按字典序排列的下一个排列。
Sample Input
8
2 6 4 5 8 1 7 3
Sample Output
8227
2 6 4 5 8 3 1 7
Hint

Source
题解:
一开始不知道什么是字典序,理解了一段时间才明白。字典序就是一个从小到大的排序,比如1,2,3排序,先用1开头,有1,2,3和1,3,2两个排列,而,因为2<3,因此1,2,3排在1,3,2前面,下一步以2开头,有2,1,3和2,3,1因为1<3,因此2,1,3在2,3,1前面。同理,可以得知后面的排列。总之,自己对于字典序的理解就是先以小数开头,找完以这个数为首的所有排列后再以次小的数开头再找,每次的排列中,也是第二个数较小的在前面······这样差不多就知道什么是字典序了,那就要找一个排列在字典序中的位置了,当然可以找出所给的排列中的所有数字,全排列这些数字,按照字典序的规则,找出这些数字的所有排列按照字典序的规则从小到大的顺序,再去看一下所给序列的位置就可以找到这个序列在字典序值。不过这样做数据量太大。
我们可以直接去寻找一个排列的字典序值,即一个排列在字典序中的位置。我们先找序列的第一个元素,比如样例中的2 6 4 5 8 1 7 3,第一个元素是2,这个序列中的所有元素是1,2,3,4,5,6,7,8而给出的序列是以2开头,说明这个序列位于所有以1开头的序列的后面,这个序列中除去1还有7个数,因此,所有以1开头的序列有17!个,按照字典序的规则,排完了以1开头的,就轮到以2开头的了,我们离寻找序列2 6 4 5 8 1 7 3的位置又近了一部,序列中的第二个数是6,于是寻找比6小的数,有4,5,1,3这四个数,如果从整个序列来看的话,序列中的所有元素是1,2,3,4,5,6,7,8,按理说比6小的还有2,但是由于这是排序,序列中不能有重复的元素,已经以2开头了,第2位上也就不可能在为2了。因此寻找比6小的数,有4,5,1,3这四个数(也就是从6这个数向后寻找序列中比它小的数),按照字典序的规则,排完了比6小的这四个数才轮到6,因此排6之前有46!个位置被占,来到了第三个数,是4,在这个数之后比它小的是1,3,因此要先排完了1,3才轮到4,因此前面又有2*5!个位置被占······依次类推直到最后,就不写了。。。
还有,下一问,找按字典序排列的下一个排列,至于这个排列之后下一个排列是什么的问题,还需要好好研究一下给出的序列,我们知道序列是先排小的数,我们观察给出的序列,从后往前看,看看从最后一个元素往前是不是升序的,当我们从后往前看发现了到了一个位置,它不升序了,成了降序,就比如2 6 4 5 8 1 7 3,从后往前看3 7升序,到了1降序了,就说明在这个位置,以1开头的已经排完了(因为1后面全是降序排列),那下一个排列我们就要找1这个位置的新的主角了,比1大的但还是比较小的一个数,由于后面的是降排的,我们从后面开始向前找,找到第一个比1大的数,就是它了,它就是原先1的位置的新主角(这时交换1和这个元素),后面展开以这个元素向后的排列,那排列是什么呢?这个元素后面的元素还是降序排列的呢!那当然不行了,要改为升序排列的才是以这个元素开始的向后的第一个排列。(因此这里将后面所有元素逆序)

#include <stdio.h>
#include <stdlib.h>
int a[21];
long long int f(int n)//求一个数的阶乘
{
    long long int y;
    if(n==1||n==0)y=1;
    else
        y=n*f(n-1);
    return y;
}
long long int position(int a[],int n)//找一个排列的位置
{
    int i,sum,x,j;
    long long int p=0;
    for(i=0; i<n; i++)
    {
        sum=0;
        x=a[i];
        for(j=i+1; j<n; j++)
        {
            if(a[j]<x)sum++;
        }
        p=p+sum*f(n-i-1);

    }
    return p;
}
void nixu(int a[],int x,int y)//将数组逆序
{
    int i=x,j=y,t;
    while(i<j)
    {
        t=a[i];
        a[i]=a[j];
        a[j]=t;
        i++;
        j--;
    }
}
void next(int a[],int n)//寻找下一个排列
{
    int i,x1,x2,t;
    for(i=n-1; i>=0; i--)
    {
        if(a[i-1]<a[i])
        {
            x1=i-1;
            break;
        }
    }
    for(i=n-1; i>=0; i--)
    {
        if(a[i]>a[x1])
        {
            x2=i;
            break;
        }
    }
    t=a[x1];
    a[x1]=a[x2];
    a[x2]=t;
    nixu(a,x1+1,n-1);

}
int main()
{
    int n,i;
    long long int x;
    scanf("%d",&n);
    for(i=0; i<n; i++)
        scanf("%d",&a[i]);
    x=position(a,n);
    printf("%lld\n",x);
    next(a,n);
    for(i=0; i<n-1; i++)
        printf("%d ",a[i]);
    printf("%d\n",a[n-1]);
    return 0;
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值