生成n個數的全排列【遞歸、回溯】

下面討論的是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 }
View Code

-----------------------------------------------------插入分割線-----------------------------------------------------數組

代碼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 }
View Code

-----------------------------------------------------分割線結束-----------------------------------------------------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 }
View Code

下面是網上對這個問題的代碼,也不錯。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 }
View Code

 

/*==============================================================================
下面的分析來自王曉東編著的《算法設計與分析(第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

 (下次再見呵呵)遞歸

相關文章
相關標籤/搜索