排列问题

按照字典序生成所有的排列,可以这样考虑,首先对输入的数组进行一个排序,不断的构建比当前在字典序上大1个的序列,直到构建到最大的序列,无法再次构建为止。具体的算法描述如下(来自参考文献[1]):

Generation in lexicographic order

There are many ways to systematically generate all permutations of a given sequence[citation needed]. One classical algorithm, which is both simple and flexible, is based on finding the next permutation in lexicographic ordering, if it exists. It can handle repeated values, for which case it generates the distinct multiset permutations each once. Even for ordinary permutations it is significantly more efficient than generating values for the Lehmer code in lexicographic order (possibly using the factorial number system) and converting those to permutations. To use it, one starts by sorting the sequence in (weakly) increasing order (which gives its lexicographically minimal permutation), and then repeats advancing to the next permutation as long as one is found. The method goes back to Narayana Pandita in 14th century India, and has been frequently rediscovered ever since.[10]

The following algorithm generates the next permutation lexicographically after a given permutation. It changes the given permutation in-place.

  1. Find the largest index k such that a[k] < a[k + 1]. If no such index exists, the permutation is the last permutation.
  2. Find the largest index l such that a[k] < a[l]. Since k + 1 is such an index, l is well defined and satisfies k < l.
  3. Swap a[k] with a[l].
  4. Reverse the sequence from a[k + 1] up to and including the final element a[n].

After step 1, one knows that all of the elements strictly after position k form a weakly decreasing sequence, so no permutation of these elements will make it advance in lexicographic order; to advance one must increase a[k]. Step 2 finds the smallest value a[l] to replace a[k] by, and swapping them in step 3 leaves the sequence after position k in weakly decreasing order. Reversing this sequence in step 4 then produces its lexicographically minimal permutation, and the lexicographic successor of the initial state for the whole sequence.

上面的描述,实际上还是有点容易让人困惑的,其实看参考文献[4]的程序在分析一下就很清楚了,下面根据参考文献[4]的程序举例说明,首先看一下程序:

int permute(char *str, int len)
{
   int key=len-1;
   int newkey=len-1;

   /* The key value is the first value from the end which
      is smaller than the value to its immediate right        */

   while( (key > 0) && (str[key] <= str[key-1]) )
      { key--; }

   key--;

   /* If key < 0 the data is in reverse sorted order, 
      which is the last permutation.                          */

   if( key < 0 )
      return 0;

   /* str[key+1] is greater than str[key] because of how key 
      was found. If no other is greater, str[key+1] is used   */

   newkey=len-1;
   while( (newkey > key) && (str[newkey] <= str[key]) )
   {
      newkey--;
   }

   swap(str, key, newkey);

   /* variables len and key are used to walk through the tail,
      exchanging pairs from both ends of the tail.  len and 
      key are reused to save memory                           */

   len--;
   key++;

   /* The tail must end in sorted order to produce the
      next permutation.                                       */

   while(len>key)
   {
      swap(str,len,key);
      key++;
      len--;
   }

   return 1;
}

我们假设已经生成了一个排列为12543,那么按照递增序下一个排列应该是13245,下面就根据程序来分析一下如何生成13245这个序列。

首先我们注意到一个事实,这5个数字生成的最后的一个序列是54321,这个数字一定是降序排列的,这样的数字才最大,任何一个数列都可以看成从第j位开始后面降序排列的数列,例如12345可以看作是j=5开始的降序排列,32415可以看作j=5的降序排列,52143,可以看作是j=4的降序排列,注意到这个特征,那么下一个比这个数列大的序列一定是j-1位和>j位中最小的大于j-1位置数的数进行调换,这样j-1位的数字就已经正确了,这个过程如下面代码所示:

1)找出j-1的位置

   int key=len-1;
   int newkey=len-1;

   /* The key value is the first value from the end which
      is smaller than the value to its immediate right        */

   while( (key > 0) && (str[key] <= str[key-1]) )
      { key--; }

   key--;
2)找出在j位和j位之后,并且大于或等于j-1位置的数的数字,按照程序来看找到的位置为newkey;经过交换之后,还是可以保证从j位置到n位置是单调递减的,这有点不好理解,但仔细想想就清楚了,因为与原来相比就newkey位置的数字发生了变化,但这个变化可能导致原来的递减序出问题吗?不会的,因为在交换前,已经做过newkey以后的数字和j-1位置的数字的比较,他们一定是比j-1位置的数字小的,因为newkey位置的寻找过程保证了这一点。

 newkey=len-1;
   while( (newkey > key) && (str[newkey] <= str[key]) )
   {
      newkey--;
   }

   swap(str, key, newkey);

下面开看看j位后面的数字如何调整?

经过上面的交换以后,12543变成了13542,通过上面的分析,我们知道经过交换后,包括j在内的以及其后面的数据一定是递减的,而下一个数列应该使j到n的数列变成递增的,这样就完成了数列的生成,递减数列变成递增数列只要首尾交换就可以了。于是13542再变换成13245,从而完成了数列的生成过程。

程序如下:

   len--;
   key++;

   /* The tail must end in sorted order to produce the
      next permutation.                                       */

   while(len>key)
   {
      swap(str,len,key);
      key++;
      len--;
   }

从而完成了下一个序列的生成。

完成的程序来自参考文献[4],完整程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void swap(char *s, int a, int b)
{
   char temp=s[a];
   s[a] = s[b];
   s[b] = temp;
}


int permute(char *str, int len)
{
   int key=len-1;
   int newkey=len-1;

   /* The key value is the first value from the end which
      is smaller than the value to its immediate right        */

   while( (key > 0) && (str[key] <= str[key-1]) )
      { key--; }

   key--;

   /* If key < 0 the data is in reverse sorted order, 
      which is the last permutation.                          */

   if( key < 0 )
      return 0;

   /* str[key+1] is greater than str[key] because of how key 
      was found. If no other is greater, str[key+1] is used   */

   newkey=len-1;
   while( (newkey > key) && (str[newkey] <= str[key]) )
   {
      newkey--;
   }
   swap(str, key, newkey);

   /* variables len and key are used to walk through the tail,
      exchanging pairs from both ends of the tail.  len and 
      key are reused to save memory                           */

   len--;
   key++;

   /* The tail must end in sorted order to produce the
      next permutation.                                       */

   while(len>key)
   {
      swap(str,len,key);
      key++;
      len--;
   }

   return 1;
}


int main()
{
   /* test data */

   char test_string[] = "abcdefgh";

   /* A short test loop to print each permutation, which are
      created in sorted order.                                */

   do  {
      printf("%s\n",test_string);
   } while( permute(test_string, strlen(test_string)) );
}


参考文献:

[1]http://en.wikipedia.org/wiki/Permutation#Algorithms_to_generate_permutations

[2]http://www.bearcave.com/random_hacks/permute.html

[3]http://www.cut-the-knot.org/do_you_know/AllPerm.shtml

[4]http://www.freewebs.com/permute/soda_submit.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值