• 生成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
    ",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("
    ");
    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
    ",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("
    ");
    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
    ",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("
    ");
    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, "	");
    33         }
    34     }
    35     fprintf(stdout, "
    ");
    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()用于交换两个表元素值。
    ==============================================================================================
    */
    其实这个算法和上述第二段代码是一个道理。

    不管如何修改,采用递归的设计方法实现的全排列,效率都不高。下面看看书上另外写的非递归代码。

     (下次再见呵呵)

  • 相关阅读:
    随笔
    洛谷
    洛谷
    洛谷
    (水题)洛谷
    洛谷
    (水题)洛谷
    洛谷
    (水题)洛谷
    (水题)洛谷
  • 原文地址:https://www.cnblogs.com/huashanqingzhu/p/3569835.html
Copyright © 2020-2023  润新知