每日一算法(3)
问题描述:
n个元素有n!个不同的排序。将这n!个排列按字典序排列,并编号为0,1,...,n-1。每个排列的编号为其字典序值。例如,当n=3时,6个不同排列的字典序值如下:
字典序值 | 0 | 1 | 2 | 3 | 4 | 5 |
排列 | 123 | 132 | 213 | 231 | 312 | 321 |
输入:第一个正整数n。第二行是n个元素{1,2,...,n}的一个排列。
输出:第一行为字典序值。 第二行为按字典序排列的下一个排列。
样例如下:
(1)输入:
3
1 3 2
输出:
1
2 1 3
输入:
8
2 6 4 5 8 1 7 3
输出:
8227
2 6 4 5 8 3 1 7
字典序值用排列来求,不是太难。难点就是在于该字典序排列的下一个排列,找了找网上的资料,下面这个方法好用。
如何得到2 6 4 5 8 1 7 3的下一个排列?
1 从尾部往前找第一个P(i-1) < P(i)的位置
2 6 4 4 5 8 1 <-- 7 <-- 3
最终找到1是第一个变小的数字,记录下1的位置i-1
2 从尾部往前找到第一个大于1的数
2 6 4 4 5 8 1 7 3 <--
最终找到3的位置,记录位置为m
3 交换位置i-1和m的值
2 6 4 4 5 8 3 7 1
4 倒序i位置后的所有数据
2 6 4 4 5 8 3 1 7
#include<stdio.h>
//排列公式
int A(int n,int m) {
int sum=1;
for(int i=1; i<=m; i++) {
sum*=n-i+1;
}
return sum;
}
//计算排列的字典序值
int S(int a[],int n,int h) { //a为数组,n为当前数组长度,h为当前数组首位
int sum=0;
int count =0;//用来计数
if(n<1)//如果数组长度小于1,说明已经到达末尾,直接返回
return 0;
int p=a[h];
for(int i=0; i<h; i++) { //用来计数,计当前数之前有小于p的数个数
if(a[i]<p)
count++;
}
for(int i=0; i<p-count-1; i++) { //之前小于p的数不会参与排列
sum+=A(n-1,n-1);
}
if(n>1) {
sum+=S(a,n-1,h+1);//递归调用,向后推一位,继续重复该过程
}
return sum;
}
//数组逆置
int reverse(int a[],int min,int n) {
int i,j,temp;
for(i=min,j=n-1; i<j; i++,j--) {
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
return 0;
}
//求出下一个字典序
int next(int a[],int n) {
int min,max,temp;
for(int i=n-1; i>=0; i--) {
if(a[i-1]<a[i]) {//从尾部开始,找出升序截止的位置,记为min
min=i-1;
break;
}
}
for(int i=n-1; i>=0; i--) {
if(a[i]>a[min]) {//从尾部开始,找出第一个大于min的数,记为max
max=i;
break;
}
}
temp=a[min];//交换min与max的值
a[min]=a[max];
a[max]= temp;
reverse(a,min+1,n);//倒序min之后的所有数据
return 0;
}
int main() {
int a;
scanf("%d",&a);
int data[a];
for(int i=0; i<a; i++) {
scanf("%d",&data[i]);
}
int c=S(data,a,0);
printf("%d\n",c);
next(data,a);
for(int i=0; i<a; i++) {
printf("%d ",data[i]);
}
return 0;
}