三行核心代码解决全排列问题

早上还在床上的时候,用手机看到了博客园上一个全排列算法。刚才,出去晒了一下太阳,突然想起那个全排列算法,

感觉还是比较的繁琐。回来后,我仔细分析了一个这个问题,觉得,实际上,3行代码就可以解决这个问题了。

顺便,也说说,递归问题的一般解决思路吧。

这个问题,非常明显,是一个 n! 复杂度的程序,如果是这样,基本上的递归结构是这样的。

f(n) = n * f(n-1)

看到这样的问题,典型的结构 应该就是 这样的结构:

f(n)
{
 for (i = 0; i < n; i++)

 {

     f(n-1)

 }
}

退出条件就是 n = 0;

当然,递归问题,必须有:

1. 结束条件

2. 递推公式

这样,首先看递推公式。

首先对位置进行编号:(因为数组下标从 0开始,那就从0 开始吧)

0, 1, 2, 3, 4, 5

里面填一些数,显然,填什么不重要,假设

a, b , c, d, e, f

好了

如果让问题规模小1 呢?

很多递归问题,其实都是可以这样分析的,假设 n - 1 的都已经算好了,

如果你没有思路,我刚开始也没有,其实,你只要在纸上画出 f(n-1) 的所有可能性(要按照一定的思路列出)。

不要怕烦,写代码的思路就是这样出来的(当然,天才或许有灵感)

a 没有:b , c, d, e, f

b 没有:a, c, d, e, f

c 没有:a, b, d, e, f

d 没有: a, b, c, e, f

e 没有:a, b, c, d, f

f 没有:  a, b, c, d, e

我写完之后,感觉就是把这f(n-1)的可能性加起来,就是所有的可能性。

就想起数学里面计算排列数的方法:

叫做乘法原理,如果一件事情,可以通过n 步完成:

第一步:

第一个位置有n 种可能,大家都可以放。

第二步:

第二个位置有n-1 种可能,因为,第一个位置定下来了。

...

最后,一步,大家都定下来了,那么最后一个位置就只能放剩下来的那个了。

虽然,这个计算过程是一步,一步的,像是一种顺序结构。但是,也可以认为是一种递归结构。

用程序模拟就是:

第一个位置 如果是a ,下面就是 f(n-1) 的问题了。

第一个位置 如果是b,   下面也是f(n -1) 的问题了

...

接下来,你就是绞尽脑子,怎么模拟这个过程了。

最后想到了交换比较的好。

就是按照顺序把元素交换到pos 位置,

交换后,对f(n-1) 进行处理

然后再交换回来,对下一个元素进行交换。

直接给出代码:

#include <stdio.h>


#define swap(arr, i , j)\
{\
    
if ((i) != (j)) {\
        
int temp;\
        temp 
= arr[i]; \
        arr[i] 
= arr[j]; \
        arr[j] 
= temp;\
    }\
}

void print_array(int *arr, int len)
{
    
int i;
    printf(
"\n");
    
for(i  =0; i < len; i++) {
        printf(
"%d ", arr[i]);
    }
}

int permutation(int *arr, int pos, int n)
{
    
int i;

    
if (pos == n) { //递归结束条件
        print_array(arr, n);
        
return;
    }

    
for(i = pos; i < n; i++
    {  
        swap(arr, i, pos);
//某个数字交换到 pos 位置
        permutation(arr, pos + 1, n); //对出pos 位置后面的数,进行一次全排列
        swap(arr, i, pos);//复原原来的排列,准备交换下一个数到pos 位置。  
    }
    
return 0;
}

int main(int argc, char *argv[])
{
    
int n = 0;
    
int i;
    
int number[20]; //已经有2432902008176640000 种排列方法了,输出来也很难保存这样多的数据了,所以程序不能大于这个数字。

    printf(
"input n: ");
    
while (scanf("%d"&n)) {
        
if (n < 1) {
            printf(
"n must big than zero. \n");
            
continue;
        }
        
if (n > 20) {
            printf(
"n too big. \n");
            
continue;
        }
        
for (i = 0; i < n; i++) {
            number[i] 
= i + 1;
        }
        permutation(number, 
0, n);
        printf(
"\n\ninput n: ");
    }
    
return 0;
}

一般的,我给大家介绍一些递归编程中,比较常用的技巧:

1. 一般,如果,不能退到 n - 1,必须退到 n - 2 和 n -1 组合才行的,一般不要用递归。否则,计算复杂性一般都很高。

2. 如果,你觉得一个问题可以 用nlogn 的复杂性来解决,一般验证一下:把问题对半,然后,看看,两个只问题能合并成

这个问题的解需要多少的计算量。如果是2f(n/2) + n 那么,问题就是nlogn 的复杂度,如果,n是线性的,或者是常数,

可以考虑递归,否则,就不要考虑了,复杂度一般会很高。

3. 如果一个问题,可以在n的复杂度内解决,一般来说,递归的时候,是常数级别的运算。这样的问题很多,常见的算法

是动态规划的算法。

很多时候,用你对一个问题复杂度的直觉,倒推出算法,是一种很有意思的方法。特别是应付考试,那是相当的有效。

(很多考试题,会要求 复杂度 是多少以内)

原文地址:https://www.cnblogs.com/niniwzw/p/1689863.html