算法设计之字典序
Posted zqq277
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法设计之字典序相关的知识,希望对你有一定的参考价值。
问题描述: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各元素{1,2,3...n}的一个排列,计算出这个排列的字典序值,以及按字典序排列的下一个排列。
问题分析:比较简单的想法就是就是将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<iostream>
#include<cstring>
using namespace std;
int section_sum(int i,int k){
//递归求法,求以i开头长度为k的升序字符串的总个数
int sum=0;
if(k==1)
return 1;
for(int j=i+1;j<=26;j++){
sum+=section_sum(j,k-1);
}
return sum;
}
int total_sum(int k){
//长度为k的升序字符串总个数
int sum=0;
for(int i=1;i<=26;i++){
sum+=section_sum(i,k);
}
return sum;
}
int main(){
char str[10];
cin>>str;
int ans=0;
int len=strlen(str);
for(int i=1;i<len;i++)//先把所有长度小于所求字符串长度的字符串的个数求出来
ans+=total_sum(i);
/*int one=str[0]-'a'+1;//第一个字符的顺序数
for(int i=1;i<one;i++)//求所有长度等于所求字符串长度且首字母在所求首字母之前的字符串个数
ans+=section_sum(i,len);*/
for(int i=0,temp=0;i<len;i++){
int num=str[i]-'a'+1;//下一位字符的顺序数
int len2=len-i;//获取当前的长度
for(int j=temp+1;j<num;j++) //计算num位置前的字符串组合次数,同时计算完以后不在重复计算
ans+=section_sum(j,len2); //调用函数 section_sum计算 num位置前的字符串组合次数
temp=num;
}
cout<<ans+1<<endl;
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]进行逆向重排。
以上是关于算法设计之字典序的主要内容,如果未能解决你的问题,请参考以下文章