Catalan数详细数学推导及多种应用详解

此题目对应于 LeetCode 96

题目要求:

Given n, how many structurally unique BST's (binary search trees) that store values 1...n?

结点数为n的不同形态的二叉树一共有多少种?这是一个很常见的面试题。

从二叉树的定义来看,二叉树本质上就是一个递归的形式,左子树,右子树,根节点。所以根节点应该不变,需要递归处理的是左右子树。

也就是说,还是考虑固定一个节点,即根节点。好的,按照这个思路,还剩2个节点,那么左右子树的分布情况为2=0+2=1+1=2+0。

所以有3个节点时,递归形式为h(3)=h(2) + h(1)*h(1) + h(2). (注意这里的乘法,因为左右子树一起组成整棵树,根据排列组合里面的乘法原理即可得出)

那么有n个节点呢?我们固定一个节点,那么左右子树的分布情况为n-1=n-1 + 0 = n-2 + 1 = ... = 1 + n-2 = 0 + n-1

OK。递归表达式出来了h(n) = h(n-1) + h(n-2)h(1) + h(n-3)h(2) + ... + h(1)h(n-2) + h(n-1)

再定义h(0)=1时,这个递推式就表示了组合数学中经典的Catalan数:

此递推式的解为:

这里我们给出这个递推式的解法,里面会包含一些数学知识和理论推导。建议博友们可以看看,如不感兴趣的可以自行跳过。

其中

最后附上Python求Catalan数的程序。

解法1:

def Catalan(n):
    if n == 0 or n == 1:
        return 1
    else:
        result = 0
        for i in range(n):
            result += Catalan(i)*Catalan(n-1-i)
        return result

上面的代码时粗暴的解法,提交LeetCode结果是运行超时

下面提供一份用于动态规划思想做的方法:

解法2:

class Solution(object):
    def __init__(self,n):
        self.dic = {} # 记录表,记录numTrees(i)的值,下次需要直接读取

    def numTrees(self, n):
        if n == 0 or n == 1:
            self.dic[n]=1
            return 1
        else:
            result = 0
            for i in range(n):
                if i in self.dic:
                    tmp1 = self.dic[i]
                else:
                     tmp1 = self.numTrees(i)
                if n-1-i in self.dic:
                    tmp2 = self.dic[n-1-i]
                else:
                     tmp2 = self.numTrees(n-1-i)
                result += tmp1*tmp2
            self.dic[n] = result
            return result

解法3:

由于这个问题有着通项公式,所以可以直接通过计算组合数的方法进行求解:

有公式f(n)=1/(n+1) * C(n, 2n) ,可知我们只需求得组合数C(n, 2n)。

# 从n个数中选择m个数的组合数C(n,m)
class Solution(object):
    def combination(self,n,m):
        tmp = 1
        if m > n-m: #取n和m-n的最小值,以减少计算量
            return self.combination(n,n-m)
        for i in range(n-m+1):
            tmp *= n-i
            tmp /= i+1
        return tmp

    def numTrees(self, n):
        return self.combination(2*n,n)/(n+1)

Catalan数的常见应用由如下几种,这里分别给出分析和详解。

应用1:结点数为n的不同形态的二叉树一共有多少种?

这个解法已经在上面的内容进行叙述。

应用2:n对括号有多少种有效匹配方式,即左右括号正确匹配?

我们先举几个例子

1对括号:()  1种可能

2对括号:()()  (())                 2种可能

3对括号:((())) ()(()) ()()() (())() (()())          5种可能

通过数列的这前几项,我们可以猜测这个数列是Catalan数,然而这里根据意义推导出递推式比较难以想象和说明,这里针对这个括号匹配的应用给出另一种表达式的推导。

(1)考虑n对括号,共有n个左括号“(”和右括号“)”。这n对左右括号的全排列,可以看做是2n个空,将n个左括号放入任意的n个空之中,剩下的位置由右括号填充。其排列数显然为C(n,2n)

(2)在所有的排列中,显然有部分是非法排列,我们从中减去非法排列个数,即可以得到合法的排列数目,现在考虑非法排列的个数。

(3)先来观察非法排列的特性,我们假设左括号“(”为1,右括号“)”为-1,非法排列有这样一个性质,对于任意一个非法排列a1,a2 ... an ,比存在一个k,使得 a1+a2+a3..ak<0。这里的想法是,假设左右括号可以这样匹配()进行抵消,这里非法的条件便是从左边出现了一个无法被抵消的右括号“)”。这里给出n=3的一个例子:

()())(,即1,-1,1,-1,-1,1,。

在k=5时出现了非法的情况。我们将1-5元素进行翻转即1和-1进行置换,该序列就变成了

-1,1,-1,1,1,这个翻转后的序列中有n+1个1,n-1个-1,共有C(n+1,2n)。这里要论证的是:

对于这种有n+1个1,n-1个-1的排列,总是存在一个最小的k,我们只需要从第1个到第k个元素翻转回去,就能变成对于有n个1,n个-1的情况下的非法排列。即有n+1个1,n-1个-1的排列与非法队列是一一对应的。

(4)所以可以推得,非法排列的个数为C(n+1,2n),最终可得结论:

对于n对括号,合法的排列共有C(n,2n) - C(n+1,2n)种.

而这就是Catalan数的另一种求解式。

**应用3:矩阵链乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?
 我们先举几个例子

1个矩阵:            a1                                1种方案

2个矩阵连乘:      a1×a2                             1种方案

3个矩阵连乘:      (a1×a2)×a3  a1×(a2×a3)       2种方案

n个矩阵连乘: a1×a2×a3×....×an 将其划分为两个部分 左边连乘和右边连乘

左边矩阵数量可以为1个,2个。。。。n-1个,右边对应为n-1个,n-2个。。。。1个

所以

应用4:一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?

(1)这个与加括号的很相似,进栈操作相当于是左括号,而进栈操作相当于右括号。n个数的进栈和出栈次序序列构成了一个

含2n个字数的序列或是n对括号的匹配序列。

(2)可以将进出栈序列分为两个部分,先进出栈1个,2个。。。n-1个元素,再进出栈n-1,n-2。。。1个元素。

所以

应用5:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?

我们先举几个例子,简单的可以自己画些图

假设有2n个点,从中取任意2点连线将原来的点划分为两个部分,其中各个部分的点数都为偶数,不然不合法,必定有连线相交,设一部分中的点数为2i,则另一部分的点数为2n-2-2i。i可以取0至2n-2。

令h(n)=f(2n)便得到了标准的Catalan递推式。

参考文献

(1)http://www.cnblogs.com/ShaneZ...
(2)https://baike.baidu.com/item/...
(3)http://blog.csdn.net/sinat_26...
(4)https://www.cnblogs.com/dztgc...
(5)https://blog.csdn.net/acdream...
(6)http://blog.sina.com.cn/s/blo...

你可能感兴趣的:(python,leetcode,数学,catalan-number)