排列問題

按照字典序生成所有的排列,可以這樣考慮,首先對輸入的數組進行一個排序,不斷的構建比當前在字典序上大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

發佈了82 篇原創文章 · 獲贊 3 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章