排列的字典序问题
问题描述: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;
}