生成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/

題目要求:原序列是字母,而且字母不重復,而且原序列按字典序升序排列,要求生成的所有排列按字典序升序輸出。

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

-----------------------------------------------------分割線結束-----------------------------------------------------

下面是書上的代碼,比較好一點,不過一開始可能不好理解。建議看懂了上面的再來看這段代碼。

 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

下面是網上對這個問題的代碼,也不錯。

 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()用於交換兩個表元素值。
==============================================================================================
*/
其實這個算法和上述第二段代碼是一個道理。

 

不管如何修改,采用遞歸的設計方法實現的全排列,效率都不高。下面看看書上另外寫的非遞歸代碼。

 (下次再見呵呵)


免責聲明!

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



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