組合數學課程上,介紹到了排列的生成算法。而其中第一個算法——翻轉算法,竟是由本課程的任課老師殷奶奶發現的,瞬間感覺到了殷奶奶的強大。殷奶奶在課堂上說,這個算法,是她盯着一個排列數看了兩年,同時結合平時她的學生的課程設計想出來的。
殷奶奶在課堂上說了算法組成的三要素:
- 算法處理后結果出來的是一張表
- 算法只能由當前一個情況生成下一個情況
- 算法應該是循環的
關於這個算法組成的三要素,現在還不是很理解。
在這里,由於對這個翻轉算法比較敢興趣,於是將這個算法進行一下簡單的介紹,同時用程序實現該算法,通過計算機來實現輸出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個數的排列如下圖:

