排列
问题描述
大家知道,给出正整数 n,则 1到 n这 n个数可以构成 n!种排列,把这些排列按照从小到大的顺序(字典顺序)列出,如 n=3时,列出 1 2 3,1 3 2,2 1 3,2 3 1,3 1 2,3 2 1六个排列。
给出某个排列,求出这个排列的下 k个排列,如果遇到昀后一个排列,则下 1排列为第 1个排列,即排列 1 2 3…n。
比如:n = 3,k=2 给出排列 2 3 1,则它的下 1个排列为 3 1 2,下 2个排列为 3 2 1,因此答案为 3 2 1。
输入数据
第一行是一个正整数 m,表示测试数据的个数,下面是 m组测试数据,每组测试数据第一行是 2个正整数 n( 1 <= n < 1024 )和 k(1<=k<=64),第二行有 n个正整数,是 1,2 … n的一个排列。
输出要求
对于每组输入数据,输出一行, n个数,中间用空格隔开,表示输入排列的下 k个排列。
输入样例
3
3 1
2 3 1
3 1
3 2 1
10 2
1 2 3 4 5 6 7 8 9 10
输出样例
3 1 2
1 2 3
1 2 3 4 5 6 7 9 8 10
解题思路
这道题目,昀直观的想法是求出 1到 n的所有排列,然后将全部排列排序……且慢,n昀大可以是 1024,1024! 个排列,几乎永远也算不出来,算出来也没有地方存放。那么,有没有公式或规律,能够很快由一个排列推算出下 k个排列呢?实际上寻找规律或公式都是徒劳的,只能老老实实由给定排列算出下一个排列,再算出下一个排列……一直算到第 k的排列。鉴于 k的值很小,昀多只有 64,因此这种算法应该是可行的。
如何由给定排列求下一个排列?不妨自己动手做一下。比如:
“21 4 7 6 3 5”的下一个排列是什么?容易,显然是“ 2 1 4 76 5 3”,那么,再下一个排列是什么?有点难了,是“2 1 5 3 4 6 7”。
以从“2 1 4 76 5 3”求出下一个排列 “ 21 5 3 4 6 7”作为例子,可以总结出求给定排列的下一个排列的步骤:
假设给定排列中的 n个数从左到右是 a1, a2, a3……an 。
1)从 an开始,往左边找,直到找到某个 aj,满足 aj-1 < a(对上例j, 这个 aj就是 7, aj-1就是 4)。
2)在 aj 、aj+1…… an中找到昀小的比 aj-1大的数,将这个数和 aj-1 互换位置(对上例, 这个数就是 5,和 4换完位置后的排列是 “ 2 1 5 7 6 4 3”)。
3)将从位置 j到位置 n的所有数(共 n-j+1个)从小到大重新排序,排好序后,新的排列就是所要求的排列。(对上例,就是将 “7 6 4 3”排序,排好后的新排列就是 “2 1 5 3 4 6 7”)。
当然,按照题目要求,如果 a1, a2, a3……an已经是降序,那么它的下一个排序就是 an, an-1, an-2……a1 。
解答:
#include <iostream>
#define MAX_NUM 1024
using namespace std;
int an[MAX_NUM+10];
int cmp(const void* a,const void* b)
{
return(*(int *)a-*(int *)b);
}
int main()
{
int M;
int n,k,i,j;
scanf("%d",&M);
for(int m=0;m<M;m++)
{
scanf("%d%d",&n,&k);
for(i=1;i<=n;i++)
scanf("%d",&an[i]);
an[0]=100000;//哨兵,确保其比数组中所有数都要大
for(i=0;i<k;i++)
{
for(j=n;j>=1 && an[j-1]>an[j];j--);
if(j>=1)
{
int min=an[j];//相当于例子中的7
int minIdx=j;
for(int kk=j;kk<=n;kk++)
if(min>an[kk] && an[kk]>an[j-1])
{
min=an[kk];//找到从an[j]到最后 最小的但比an[j-1]大的数,并记录其下标
minIdx=kk;
}
//交换这个位置的值
an[minIdx]=an[j-1];
an[j-1]=min;
qsort(an+j,n-j+1,sizeof(int),cmp); //从j到最后对这段进行排序
}
else//如果已经是降序了
{
for(j=1;j<=n;j++)
an[j]=j;
}
}
for(j=1;j<=n;j++)
printf("%d",an[j]);
printf("\n");
}
system("pause");
return 0;
}
qsort函数并不要求第一个参数必须是一个数组的开始地址,只要是待排序的一片连续空间的开始地址即可。同样, qsort的第二个参数也不必一定是整个数组的元素个数,只要是待排序的元素个数即可。
1.把排列存放在 an[1] .... an[n],而在 an[0]存放一个比排列中所有的数都大的数,这个 an[0]所起的作用通常称之为“哨兵”。有了“哨兵”,就可以写语句 23: for( j = n ; j >= 1 && an[j-1] > an[j] ; j -- ) ; 而 23不必担心 j-1小于 0导致数组越界。如果没有“哨兵”,而且将排列存放在 an[0] .... an[n-1]中,那么写到相当于语句 23的这个 for循环的时候,就要判断 j-1小于 0的情况,比较罗嗦,也容易出错。放置“哨兵”,是在数组或链表中进行各种操作时常用的做法。
2. 学过 C++标准模板库的同学会注意到,用标准模板库中的 next_permutation 算法直接就能求给定排列的下一个排列,根本不需动脑筋。
3.这个题目的测试数据比较多,用 scanf读入没有问题,有的同学学了点 C++,用 C++中的 cin读入数据,就会造成超时。