全排列递归实现的讨论

 给出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)

posted @ 2009-09-16 13:49  lzprgmr  阅读(1021)  评论(1编辑  收藏  举报

黄将军