练习:排列的字典序问题

排列的字典序问题

问题描述:n个元素{1,2,...,n}有  n!  个不同的排列,将  n! 个排列按字典序列排序,并编号0 — n!-1 。每个排列的编号为其字典序值。计算出相应的字典序值和按字典序排列的下一个排列        

            字典序值     0         1         2        3        4        5

             排列         123    132     213   231   312    321

sample:input

                  8

                   2 6 4 5 8 1 7 3

                  Output

                  8227

                  2 6 4 5 8 3 1 7

       先说字典序值,由于是非重复元素的排列,因此每一种元素排第一的次数都是相同的,这就可以得出当某个数字排头时,它至少是从第几位(哪个字典序值)开始,当计算完第一位后,盖掉第一位数字,把第二位数字当排头又来计算,这样就形成了递归算法,把算出的序值加起来就得到最后结果。但有个必须注意的是,盖掉的数字绝对不能忽略,计算第二位数字的时候,已经是少了一位数字进行排列了,计算第三位时,又少了2个......所以这时必须进行相应处理。

下面的num函数就是对缺失的数字进行处理的,在计算第n位数字时,如果a[n]都比前面几位都小的,那是没有影响,因为比它大的数字的缺失影响不了它的排序

#include <iostream>
#include <math.h>
#include <algorithm>
using namespace std;
int num(int a[],int n)
{
    int cou=0;
    for(int i=0; i<n; i++)   
        if(a[n]>a[i])                                    //计算n位之前比它小的数字
            cou++;
    return a[n]-cou-1;
}
int dictionary(int a[],int k,int n,int length)
{
    int page=1;
    if(n!=length-1)
    {
        if(n==0)
            for(int i=1; i<=length; i++)                 //利用数学方法求解总的排列数
                k=k*i;
        k=k/(length-n);
        page=k*num(a,n)+dictionary(a,k,n+1,length);
    }
    else            
        return 0;                                        //最后一位只有它自己了,所以是0
    return page;
}

接下来是按字典序排列的下一个排列,这时我们应该从后面开始比较起来,从后面2个开始,然后3个,4个...每次又先把区间最后一个元素当做关键元素前面的元素逐个与其对比,若关键元素最小的,则又把倒数第二个元素当做关键元素,又进行对比,对比完区间中的元素,如果每次的关键元素前面的元素都比它小的话,就进入下一个区间进行比较。

/*
void next(int a[],int length)
{
    int mark=1;
    for(int i=length-2; i>=0&&mark==1; i--)
    {
        int k=length-1,ma=0,j=0;
        if(a[k]==1)
        {
            for(k=length-2; k>i&&ma==0; k--)
                for(j=k-1; j>=i&&ma==0; j--)
                    if(a[j]<a[k])
                        ma=1;
            k++;
            j++;
        }
        else
            for(int n=length-2; n>=0; n--)
                if(a[n]<a[k])
                {
                    j=n;
                    ma=1;
                    break;
                }
        if(ma==1)
        {
            swap(a[j],a[k]);
            sort(a+j+1,a+length);
            break;
        }
*/


但换一个角度想,之所以要进入下一个区间,是因为目前的区间刚好以从大到小的顺序排序,所以要扩大区间,就是向前取多一位数,所以如果出现一个数不是按照从小到达顺序排的那必然就是这个新取进来的数,这时只要再从比它大的数找到一个最小的就好了,这样就可以避免了上面三重循环了,简洁、高效了很多

void next(int a[],int length)
{
    for(int i=length-2; i>=0; i--)
    {
        int k=length-1,ma=0;
        if(a[k]==1)
        {
            for(; k>=i; k--)
                if(a[i]<a[k])
                {
                    ma=1;
                    break;
                }
        }
        else
            for(int j=length-2; j>=0; j--)
                if(a[j]<a[k])
                {
                    i=j;
                    ma=1;
                    break;
                }
        if(ma==1)
        {
            swap(a[i],a[k]);
            sort(a+i+1,a+length);
            break;
        }
    }
}

把剩下代码贴上来

void dictionary_order(int a[],int k,int n,int length)
{
    int page=dictionary(a,k,n,length);
    cout<<page<<endl;
    int j=1;
    for(int i=1; i<=length; i++)
        j=j*i;
    if(j!=page+1)                                         //这里对是否是该排列中的最后一个进行了判断
    {
        next(a,length);
        for(int i=0;i<length;i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }
    else
        cout<<"it is the last number"<<endl;
}
int main()
{
    while(1)
    {
        int length=0;
        cin>>length;
        int a[length];
        for(auto &aa:a)
            cin>>aa;
        dictionary_order(a,1,0,length);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值