全错位排列问题

来源:互联网 发布:东莞网站建设优化 编辑:程序博客网 时间:2024/04/23 18:13

问题描述

这是一个很经典的数学问题:有一个人写了n封信件,对应n个信封,然而粗心的秘书却把所有信件都装错了信封,那么一共有多少种装错的装法?

数学抽象

这个问题可抽象为以下一个数学问题:已知一个长度为n的有序序列{a1,a2,a3,…,an},打乱其顺序,使得每一个元素都不在原位置上,则一共可以产生多少种新的排列?
例如:原序列为{a,b,c,d,e},则新序列{b,c,d,e,a}为其一个全错位排列,新序列中每一个元素都不在原来的位置上。

问题解决

首先考虑几种简单的情况:

  • 原序列长度为1
    序列中只有一个元素,位置也只有一个,这个元素不可能放在别的位置上,因此原序列长度为1时该为题的解是0。
  • 原序列长度为2
    设原序列为{a,b},则全错位排列只需将两个元素对调位置{b,a},同时也只有这一种可能,因此原序列长度为2时该问题的解是1。
  • 原序列长度为3
    设原序列为{a,b,c},则其全错位排列有:{b,c,a},{c,a,b},解是2。
  • 原序列长度为4
    设原序列为{a,b,c,d},则其全错位排列有:{d,c,a,b},{b,d,a,c},{b,c,d,a},{d,a,b,c},{c,d,b,a},{c,a,d,b},{d,c,b,a},{c,d,a,b},{b,a,d,c},解是9。

随着n的增大,该问题的解也迅速增大,使用列举的方法进行求解显然是不明智的。数学家欧拉给了我们一种解决该问题的递推公式:
设长度为n的序列的全错位排列一共有f(n)种,假设我们已经解决了f(1)到f(n-1),那么当序列新增了一个元素an,显然全错位排列中该元素不能放在第n个位置上,假设该元素在从1到n-1的第i个位置,那么在新序列中第n个位置上的元素可能有两种情况:

  1. 第n个位置上的元素为ai
    因为an和ai都不在原位置上,因此只需剩余的元素都是全错位排列,新序列就构成了全错位排列。那么除去ai和an还剩下n-2个元素,则这n-2个元素一共有f(n-2)种全错位排列,因为i的选择共有n-1种,因此该情况下一共有(n-1)*f(n-2)种全错位排列。
  2. 第n个位置上的元素不为ai
    该种情况相当于,前n-1个元素做好了全错位排列,an与其中任意元素交换位置,新生成的序列也是一个全错位排列。这种情况下i的选择共有n-1种,n-1的元素的全错位排列共有f(n-1)种,因此该情况下一共有(n-1)*f(n-1)种全错位排列。

综合以上两种情况,f(n)=(n-1)f(n-2)+(n-1)*f(n-1)=(n-1)[f(n-2)+f(n-1)]
显然这个公式适用于n>2的情况,而f(1)=0,f(2)=1是之前已经列举得出的。
将n=3代入,得到f(3)=2*(0+1)=2,将n=4代入,得到f(4)=3*(1+2)=9,与列举所得到的结果相同。

编程实现

  • 递归方法
    很容易想到的一种编程方式就是递归方法,其代码如下:
int recursive(int n){    if (n==1)        return 0;    if (n==2)        return 1;    return (n-1)*(recursive(n-2)+recursive(n-1));}

递归方法思路清晰,实现容易,但我们应看到在这个问题中,存在大量的重复子问题,因此使用递归方法的计算效率很低。

  • 非递归方法
    非递归方法则是利用循环,从下到上依次求出f(1),f(2),f(3),…,f(n-1),f(n)的值,可使用一个数组依次存下这组解,但也可以节约存储空间,只存储计算时需要的f(n-2)和f(n-1)两个数值,其代码如下:
int non_recursive(int n){    int a=0,b=1;    int r=0;    if (n==1)        return a;    if (n==2)        return b;    for (int i=2;i<n;i++)    {        r=i*(a+b);        a=b;        b=r;    }    return r;}

通过验证,这两段代码均可计算全错位排列问题。

1 0
原创粉丝点击