排列的字典序问题

每日一算法(3)
问题描述:
n个元素有n!个不同的排序。将这n!个排列按字典序排列,并编号为0,1,...,n-1。每个排列的编号为其字典序值。例如,当n=3时,6个不同排列的字典序值如下:

字典序值012345
排列123132213231312321

输入:第一个正整数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;
}

 

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值