下面討論的是n個互不相同的數造成的不一樣排列的個數。
畢竟,假如n個數當中有相同的數,那n!種排列當中確定會有一些排列是重複的,這樣就是一個不同的問題了。
/*===================================== 數的全排列問題。將n個數字1,2,…n的全部排列枚舉出來。 2 3 1 2 1 3 3 1 2 3 2 1 思路: 遞歸函數定義以下: void fun(int a[],int flag[],int i,int ans[]); //原始數據存放於a[]。flag[]的每個元素標記a[]對應位置處的元素是否已經被選用。 //i表示當前對某排列而言,正在尋找到第i個數據。 //ans[]是存放每個排列的地方。每當放夠n個元素到ans則開始打印該數組。 程序中先把原始數據讀入到數組a[]。flag[]數組元素所有置0. 生成一個排列的過程能夠這樣認爲: 每次遞歸都掃描flag[],尋找一個flag[i]==0而後把a[i]選到排列當中。 (也即放到ans[i]當中。選a[i]後要把flag[i]置1.) 這樣就肯定了當前排列的第i個元素。 而後遞歸進入下一層肯定第i+1個數。 以此類推,逐層遞歸直至i==n-1(由於i從0開始,因此是i==n-1)就認爲已經獲得了一個 完整的排列。這個時候能夠把ans數組輸出了。 輸出後能夠開始回溯了。 每次回溯都要把flag[i]置0以便還原現場。(至關於ans[i]不選a[i]了。) 置0後繼續循環枚舉當前位置的其餘可能取值。 ======================================*/
下面是我本身按照本身的理解作的,其實有點浪費空間了:算法
1 #include<stdio.h> 2 int n,count;//n表示參與排列的數據的個數,count表示不一樣排列的個數 3 void fun(int a[],int flag[],int i,int ans[]); 4 //原始數據存放於a[]。flag[]的每個元素標記a[]對應位置處的元素是否已經被選用。 5 //i表示當前對某排列而言,正在尋找到第i個數據。 6 //ans[]是存放每個排列的地方。每當放夠n個元素到ans則開始打印該數組。 7 int main() 8 { 9 int i; 10 int a[20],flag[20],ans[20]; 11 freopen("5.out","w",stdout); 12 scanf("%d",&n); 13 for(i=0;i<n;i++) 14 { 15 flag[i]=0; 16 a[i]=i+1; 17 } 18 count=0; 19 fun(a,flag,0,ans);//從第0號開始出發,依次肯定每個位置的數值 20 printf("total:%d\n",count); 21 return 0; 22 } 23 void fun(int a[],int flag[],int i,int ans[]) 24 { 25 int j,k; 26 if(i==n-1) 27 { 28 for(j=0;j<n;j++) 29 { 30 if(flag[j]==0)//找到了一個排列,把這個排列給輸出。 31 { 32 ans[i]=a[j]; 33 for(k=0;k<n;k++) printf("%d ",ans[k]); 34 printf("\n"); 35 count++; 36 return ; 37 } 38 } 39 } 40 else 41 { 42 for(j=0;j<n;j++) 43 { 44 if(flag[j]==0) 45 { 46 ans[i]=a[j]; 47 48 flag[j]=1; 49 fun(a,flag,i+1,ans); 50 flag[j]=0; 51 } 52 } 53 } 54 }
-----------------------------------------------------插入分割線-----------------------------------------------------數組
代碼OJ測試:http://ica.openjudge.cn/dg3/solution/10102944/ide
題目要求:原序列是字母,並且字母不重複,並且原序列按字典序升序排列,要求生成的全部排列按字典序升序輸出。函數
AC代碼:(把上面的代碼作簡單修改便可) 測試
1 #include<stdio.h> 2 #include<string.h> 3 4 int n,count;//n表示參與排列的數據的個數,count表示不一樣排列的個數 5 void fun(char a[],int flag[],int i,char ans[]); 6 //原始數據存放於a[]。flag[]的每個元素標記a[]對應位置處的元素是否已經被選用。 7 //i表示當前對某排列而言,正在尋找到第i個數據。 8 //ans[]是存放每個排列的地方。每當放夠n個元素到ans則開始打印該數組。 9 10 int main() 11 { 12 int i; 13 int flag[20]; 14 char a[20],ans[20]; 15 scanf("%s",a); 16 //scanf("%d",&n); 17 n=strlen(a); 18 for(i=0;i<n;i++) 19 { 20 flag[i]=0; 21 //a[i]=i+1; 22 } 23 count=0; 24 fun(a,flag,0,ans);//從第0號開始出發,依次肯定每個位置的數值 25 //printf("total:%d\n",count); 26 return 0; 27 } 28 void fun(char a[],int flag[],int i,char ans[]) 29 { 30 int j,k; 31 if(i==n) 32 { 33 for(k=0;k<n;k++) printf("%c",ans[k]); 34 printf("\n"); 35 count++; 36 return ; 37 } 38 else 39 { 40 for(j=0;j<n;j++) 41 { 42 if(flag[j]==0) 43 { 44 ans[i]=a[j]; 45 46 flag[j]=1; 47 fun(a,flag,i+1,ans); 48 flag[j]=0; 49 } 50 } 51 } 52 }
-----------------------------------------------------分割線結束-----------------------------------------------------spa
下面是書上的代碼,比較好一點,不過一開始可能很差理解。建議看懂了上面的再來看這段代碼。設計
1 #include<stdio.h> 2 int n,a[20]; 3 long count=0; 4 void fun(int k); //fun(k)表示如今正在肯定某個排列當中的第k個數 5 //也能夠認爲fun(k)是生成第k個數到第n個數的全部排列。 6 int main() 7 { 8 int i; 9 scanf("%d",&n); 10 for(i=0;i<n;i++) 11 { 12 a[i]=i+1; 13 } 14 fun(0); 15 printf("total:%d\n",count); 16 return 0; 17 } 18 void fun(int k)//fun(k)表示如今正在肯定某個排列當中的第k個數 19 { 20 int j,t; 21 if(k==n) 22 { 23 count++; 24 for(j=0;j<n;j++) printf("%d ",a[j]); 25 printf("\n"); 26 return ; 27 } 28 else 29 { 30 for(j=k;j<n;j++)//注意:這裏是在原始數組內交換數據實現排列,因此j從k開始 31 { 32 t=a[k];a[k]=a[j];a[j]=t; 33 fun(k+1); 34 t=a[k];a[k]=a[j];a[j]=t; 35 } 36 } 37 }
下面是網上對這個問題的代碼,也不錯。code
1 #include <stdio.h> 2 3 void print(int array[], int end); 4 void swap(int &a, int &b); 5 void permute(int array[], int begin, int end); 6 7 void permute(int array[], int begin, int end) 8 { 9 if (begin == end) 10 { 11 print(array, end); 12 } 13 else 14 { 15 for (int j = begin; j <= end; ++j) 16 { 17 swap(array[j], array[begin]); 18 permute(array, begin+1, end); //recursive,enlarge problem's scope 19 swap(array[j], array[begin]); //backtracking 20 } 21 } 22 return; 23 } 24 25 void print(int array[], int end) 26 { 27 for (int i = 0; i <= end; ++i) 28 { 29 fprintf(stdout, "%d", array[i]); 30 if (i != end) 31 { 32 fprintf(stdout, "\t"); 33 } 34 } 35 fprintf(stdout, "\n"); 36 return; 37 } 38 39 void swap(int &a, int &b) 40 { 41 int temp = a; 42 a = b; 43 b = temp; 44 return; 45 } 46 47 int main() 48 { 49 int array[]={1,2,3,4}; 50 permute(array,0,3); 51 return 0; 52 }
/*============================================================================== 下面的分析來自王曉東編著的《算法設計與分析(第2版)》,清華大學出版社出版 , 第2章遞歸與分治策略例題2.4排列問題。
設R={r1,r2,r3,...,rn}是要進行排列的n個元素,Ri=R-{ri}。 集合X中的元素的全排列記爲perm(X)。(ri)perm(X)表示在全排列perm(X)的每個 排列前加上前綴ri獲得的排列。R的全排列可概括定義以下: 當n=1時,perm(R)=(r),其中r是集合R中惟一的一個元素。 當n>1時,perm(R)由(r1)perm(R1),(r2)perm(R2),...... ,(rn)perm(Rn)構成。 依此遞歸定義,能夠設計產生perm(R)的遞歸算法以下: (詳見下面代碼) ================================================================================*/
public static void perm(Object[] List,int k,int m) {//產生list[k:m]的全部排列 if(k==m) {//只剩一個元素 for(int i=0;i<=m;i++) System.out.print(List[i]); System.out.println(); } else { //還有更多個元素,遞歸產生排列 for(int i=k;i<=m;i++) { MyMath.swap(List,k,i); perm(List,k+1,m); MyMath.swap(List,k,i); } } }
/*============================================================================================ 算法perm(list,k,m)遞歸地產生全部前綴是list[0:k-1]且後綴是list[k:m]的全排列的全部排列。
調用算法perm(list,0,n-1)則產生list[0:n-1]的全排列。
在通常狀況下,k<m。算法將list[k:m]中每個元素分別與list[k]中的元素交換,而後遞歸地計算list[k+1:m]的全排列,
並將結果做爲list[0:k]的後綴。
MyMath.swap()用於交換兩個表元素值。
==============================================================================================*/
其實這個算法和上述第二段代碼是一個道理。
無論如何修改,採用遞歸的設計方法實現的全排列,效率都不高。下面看看書上另外寫的非遞歸代碼。blog
(下次再見呵呵)遞歸