排列

问题描述

大家知道,给出正整数 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读入数据,就会造成超时。

posted on 2010-04-16 09:18  蓝牙  阅读(144)  评论(0编辑  收藏  举报