• 全排列递归实现的讨论


     给出1, 2, 3, 4四个数, 请编程输出其全排列, 如:

    1 2 3 4
    1 2 4 3
    1 3 2 4
    1 3 4 2
    ...

    这样的题, 我们在学校的时候一般都遇到过,而我们最先能想到的,应该就是递归实现了,因为这和我们我理解的数学中的排列组合比较一致:先取第一个数,有4种可能,再在剩下的3个数种取出第二个数,这又有3种可能,这样下去直到取到最后一个数。 这样,4个数的全排列就有4*3*2 = 24个。n个数的全排列就是n*(n-1)*(n-2)*...*2*1. 按照这个描述, 我们发现有两点在程序中递归实现时十分重要:
    1. 哪些数已经取过了而哪些数又是没有取过,可以用的?
    2. 现在取的是哪一个数。
    确保了这两个信息,我们的递归实现就没有什么问题了。对于第一个问题,我们有两种方法可以实现:
    1) 用一个对应的bool型数组来记录被排列数组的使用状态,这个状态在递归过程中需要回溯
    2) 用一个ILLEGAL值来表示不是属于排序的数,排列数组中的数一旦被使用,就用这个值来覆盖,当然,递归过程中此值也需要回溯。
    同样,现在取得是哪个数,我们也有两种方法来表示:
    1) 用参数的方式来表明这次递归调用是为了得第几个值、
    2) 用一个静态变量来表示当前递归的深度,此深度值表明了我当前取的是哪个数。

    上面两点的两种解决方法排列组合一下:),我们就有4种方法

    首先是定义最大数组长度与非法值

    #define N 10
    #define ILLEAGALNUM  -100

    下面列出每一种实现:

    //数组存使用状态,调用深度表示取第几个数
    void Permutation1(int a[], int n)
    {
        
    static int out[N]; // result array
        static bool m[N] = {1,1,1,1,1,1,1,1,1,1}; // mark array, indicate whether the coorespond element 
                                                  
    //in array a is already used.
        static int depth = -1;                      //recursive call depth.
        depth++;
        
    for(int i = 0; i < n; ++i)
        {
            
    if(depth == n)                      // if we already get the last num, print it
            {
                
    static int l = 1;
                printf(
    "%3d: ", l++);
                
    for(int k = 0; k<n; k++) printf("%d "out[k]);
                printf(
    " ");
                depth
    --;
                
    return;
            }
            
    else if(true == m[i])                      // if element i not used
            {
                
    out[depth] = a[i];
                m[i] 
    = false;                      // mark element i as used
                Permutation1(a, n);                      // recursive to get next num
                m[i] = true;                      // backdate , so that we can try another case
            }
        }
        depth
    --;
    }

    //修改数据数组表示其使用状态,参数表示取第几个数
    void Permutation2(int a[], int index, int n)
    {
        
    static int out[N];
        
    for(int i = 0; i < n; ++i)
        {
            
    if(index == n)                      //index > n-1, try to get the n-1 num, means it is ok , printf it
            {
                
    static int l = 1;
                printf(
    "%3d: ", l++);
                
    for(int k = 0; k<n; k++) printf("%d "out[k]);
                printf(
    " ");
                
    return;
            }
            
    else if(a[i] != ILLEAGALNUM)
            {
                
    out[index] = a[i];
                a[i] 
    = ILLEAGALNUM;
                Permutation2(a, index
    +1, n);
                a[i] 
    = out[index];
            }
        }
    }

    //修改数据数组表示其使用状态,调用深度表示取第几个数
    void Permutation3(int a[], int n)
    {
        
    static int out[N];
        
    static int depth = -1;                      //recursive call depth.
        depth++;
        
    for(int i = 0; i < n; ++i)
        {
            
    if(depth == n)                      //index > n-1, try to get the n-1 num, means it is ok , printf it
            {
                
    static int l = 1;
                printf(
    "%3d: ", l++);
                
    for(int k = 0; k<n; k++) printf("%d "out[k]);
                printf(
    " ");
                depth
    --;
                
    return;
            }
            
    else if(a[i] != ILLEAGALNUM)
            {
                
    out[depth] = a[i];
                a[i] 
    = ILLEAGALNUM;
                Permutation3(a, n);
                a[i] 
    = out[depth];
            }
        }
        depth
    --;
    }


    //额外的数组表示其使用状态,参数表示取第几个数
    void Permutation4(int a[], int index, int n)
    {
        
    static int out[N]; // result array
        static bool m[N] = {1,1,1,1,1,1,1,1,1,1}; // mark array, indicate whether the coorespond element 
                                                  
    //in array a is already used.
        for(int i = 0; i < n; ++i)
        {
            
    if(index == n)                      // if we already get the last num, print it
            {
                
    static int l = 1;
                printf(
    "%3d: ", l++);
                
    for(int k = 0; k<n; k++) printf("%d "out[k]);
                printf(
    " ");
                
    return;
            }
            
    else if(true == m[i])                      // if element i not used
            {
                
    out[index] = a[i];
                m[i] 
    = false;                      // mark element i as used
                Permutation4(a, index+1, n);                      // recursive to get next num
                m[i] = true;                      // backdate , so that we can try another case
            }
        }
    }

    虽然对于这样的问题效率与空间相差不会特别明显,但是我们还是来比较一下来找出最佳的一个。对于数组使用状态的保存,显然,用第一个方案需要动用一个额外的数组,而并没有提高效率,所以我们应该采用第二个方案:修改数组值的方法。对于当前取的是哪个数,如果我们用传参数的方式,因为在排列过程中,这个递归函数被调用的次数是非常多的。(6个数的全排列就要调用1957次),这样多一个参数, 其每次调用压栈出栈的消耗就显得比较大了, 所以我们推荐用调用深度来表示。

    经过上面的讨论, Permutation3就是我们的最佳选择。 

    (搬自以前blog, 2007-08-26)

  • 相关阅读:
    C++的开源跨平台日志库glog学习研究(一)
    C++实现的字符串模糊匹配
    Git&GitHub学习日志
    UTF-8和GBK等中文字符编码格式介绍及相互转换
    HDU
    340. 通信线路(分层图最短路)
    ACwing 你能回答这些问题吗(线段树求最大连续字段和)
    Laptop(线段树+离散化)
    Infinite Inversions(树状数组+离散化)
    HDU-4417-Super Mario(主席树解法)
  • 原文地址:https://www.cnblogs.com/baiyanhuang/p/1730735.html
Copyright © 2020-2023  润新知