排列的生成算法(一)


  組合數學課程上,介紹到了排列的生成算法。而其中第一個算法——翻轉算法,竟是由本課程的任課老師殷奶奶發現的,瞬間感覺到了殷奶奶的強大。殷奶奶在課堂上說,這個算法,是她盯着一個排列數看了兩年,同時結合平時她的學生的課程設計想出來的。

  殷奶奶在課堂上說了算法組成的三要素:

  •   算法處理后結果出來的是一張表
  •   算法只能由當前一個情況生成下一個情況
  •   算法應該是循環的

  關於這個算法組成的三要素,現在還不是很理解。

  在這里,由於對這個翻轉算法比較敢興趣,於是將這個算法進行一下簡單的介紹,同時用程序實現該算法,通過計算機來實現輸出n位數的所有排列。

  翻轉算法:

  假設一個集合{1,2,…,n},構造這個集合的所有排列可以按照下面兩步進行:

  (1)將整數n插入集合{1,2,…,n-1}的每一個排列后,得到集合{1,2,…,n}的(n-1)!個不同的全排列;

  (2)選定(1)中的一個排列,依次將該排列的前i位(i=1,2…,n-1)調到該排列的尾部,得到集合{1,2,…,n}的n個不同排雷。

  通過以上兩步,由乘法原則可知,{1,2,…,n}的(n-1)!*n=n!個不同的排列。

  對於以上的構造過程,舉個例子進行解釋:

  假設已經知道了{1,2,3}的所有的全排列為:

  {1,2,3}、{2,3,1}、{3,1,2}、{2,1,3}、{1,3,2}、{3,2,1}  

  根據{1,2,3}得到{1,2,3,4}的所有排列的過程如下:

  

  圖片中第一列是如何生成的?

  首先選擇{1,2,3},然后將4插入到這個排列的末尾,得到{1,2,3,4},再然后,每次進行一次循環左移,就可以得到2341、3412、4123這幾個排列了。對於每一列,進行的步驟都統第一列。就這樣,得到了{1,2,3,4}的所有排列情況。

  一開始,感覺這很簡單啊,沒什么了不起的地方嘛。但是,真正厲害的地方不在這,而在與如何從4123變化到2314(即第一列的末尾變到第二列的頭)?如何從4231變化到3124?這其中的規律是什么?這就是翻轉算法的精華的所在。不然,這個就不是一個算法了,因為不滿足“循環”這個條件。

  我們用P4、P3、P2、P1分別表示四個數的位置,P4表示一個排列的第一位,P3表示第二位,其他依次類推。

  我們從左到右掃描一個排列,當發現第一個Pk(k表示下標)不等於k的時候,即找出第一個該位置上的數不等於他的下標的位置。我們將該位置(包括該位置本身)以前的所有的數翻轉后,連接到該排列的末尾,得到一個新的排列。通過這個方法,可以解決上面說的如何從第一列末尾跳到第二列開頭的問題。也正是因為這個方法,我們可以生成任意n個數的所有的排列。該算法總結起來如下:

  (1)從左到右找到第一個這樣的位置:他的下標和他的數的值不一樣。

  (2)將包括該數在內的之前的數進行翻轉,並接到該排列的末尾,得到一個新的排列

  (3)直到所有數都滿足該位置的數的值與下標的值一致時程序結束。

  其代碼實現如下所示:

/*
Author:xiongmao
Date:2015\11\26 16:26
Title:翻轉算法實現n個數的排列的生成 
*/
#include <iostream> 
#include <algorithm>
#include <cstring>
using namespace std;
#define N 100
int c[N];
int myreverse(int arr[],int i,int j)
{
    while(i<=j){
        int temp;
        temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
        i++;
        j--;
    }
    return 0;
}
int show(int n)
{
    if(n>=100) return -1;
    //按順序初始化並輸出第一個排列,第一個排列為1~n 
    for(int i=1;i<=n;i++){
        c[i]=i;
        cout<<i<<" "; 
    }
    cout<<endl;
    int flag=false;
    while(!flag){
        int index=n;
        for(int i=1;i<=n;i++){
            if(c[i]!=index){
                break;
            }
            index--;
        }
        if(index==0){
            for(int i=1;i<=n;i++){
                cout<<c[i]<<" ";
            }
            cout<<endl;
            flag=true;
            continue;
        }
        myreverse(c,1,n-index+1);
        int temp[N];
        int num=1;
        index=n-index+1;//使得index變為所指向的數的下標 
        for(int i=index+1;i<=n;i++){
            cout<<c[i]<<" ";
            temp[num]=c[i];
            num++;
        }
        for(int i=1;i<=index;i++){
            cout<<c[i]<<" ";
            temp[num]=c[i];
            num++;
        }
        cout<<endl;
        //將當前這個排列重新賦給數組c,用於下次循環 
        memcpy(c,temp,sizeof(c));
    }
}
int main()
{
    int n;
    while(cin>>n&&n!=-1)
    {
        show(n);
    }
}

  生成的4個數的排列如下圖:

  

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM