什么是卡特兰数

#简介

卡特兰数是组合数学中一个常出现在各种计数问题中的数列。以比利时的数学家欧仁·查理·卡塔兰 (1814–1894)的名字来命名。
数列的前几项为:1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862,…

  • 卡特兰数Cn满足以下递推关系:
    • Cn+1 = C0Cn + C1Cn-1 + ··· + CnC0

##原理

  • 设h(n)为catalan数的第n+1项,令h(0)=1,h(1)=1,catalan数满足递推式:
  • h(n)= h(0) * h(n-1) + h(1) * h(n-2) + ··· + h(n-1) * h(0),(n>=2)
  • 例如:
    • h(2) = h(0) * h(1) + h(1) * h(0) = 11 + 11 = 2
    • h(3) = h(0) * h(2) + h(1) * h(1) + h(2) * h(0) = 12+11+2*1 = 5

#应用

实质上都是递推等式的应用
关于卡特兰数应用实例有很多,有一本书就讲了将近200个“卡特兰数”问题,由于篇幅原因,这里不做过多赘述,有兴趣的同学可以深入研究

##进出栈问题

###问题描述

n个元素进栈序列为:1,2,3,4,…,n,则有多少种出栈序列。

思路

  • 我们将进栈表示为 +1,出栈表示为 -1,则 1 3 2 的出栈序列可以表示为:+1 -1 +1 +1 -1 -1。

  • 根据栈本身的特点,每次出栈的时候,必定之前有元素入栈,即对于每个 -1 前面都有一个 +1 相对应。

  • 因此,出栈序列的 所有前缀和 必然大于等于 0,并且 +1 的数量 等于 -1 的数量。

  • 接下来非法序列怎么求?乍看之下完全没思路啊,如果用暴力解法那未免不要太麻烦,这时候我们采用逆向思维,就会发现这个问题并没有想象的那么难,公式如下:

    合法序列数 = 总序列数 - 非法序列数
  • 所以我们只需要求出总序列数和非法序列数然后相减即可得出合法序列数,总序列数很好求,即 C(n)(2n) ,那么非法序列数怎么求呢?

  • 还是逆向思维,说实话直接求非法序列数同样比较难求,但是我们可以这样:

求得与非法序列数一一对应的另一种序列数,为了方便描述,这里称非法序列为 A,与 A 相对应的另一种序列为 B,对于B的定义是这样的:由于 A 是一个非法序列,必然存在某个位置的前缀和小于0的情况,那么,从左往右看,可以肯定的是,从某个位置开始,该位置前缀和突然小于0,且必然是-1(因为该位置是第一次前年缀和小于 0 的位置),比如序列:+1 -1 -1 +1 -1 +1,第三个1的前缀和首次小于0,那么我们就将该位置(包含该位置)之前的数字取反就得到了与A相对应的的序列 B:-1 +1 +1 +1 -1 +1,(怎么证明A和B是一一对应的关系呢?这么后面会讲,这里先记着肯定是一一对应的即可),这时候求得序列B的个数,即可得出 A 的个数,那么怎么求 B 的个数呢?
对于 B,由于其是将A的第一个前缀和小于零的位置(包含该位置)之前的数取反得来的,所以每个 B 都有 (n + 1) 个 +1 以及 (n - 1) 个 -1,那么 B 的数量为 C(n+1)(2n) ,则 A 的数量同为 C(n+1)(2n)

则合法序列数 = 总序列数 - 非法序列数 = C(n)(2n) - C(n+1)(2n)

  • 还有个问题,那么怎么证明A和B是一一对应的关系呢?

    • 因为每个 A 只有一个"第一个前缀和小于 0 的前缀",所以每个 A 只能产生一个 B。而每个 B 想要还原到 A,就需要找到"第一个前缀和大于 0 的前缀",显然 B 也只能产生一个 A
  • 因此同时满足以下条件即可转化为卡特兰数问题

    • ① +1和-1的个数相同皆为n个(这里+1和-1是虚指,代表两种事物,且只有这两种)
    • ② 序列每个位置的前缀和均大于等于0
      最终,满足该条件的序列数为:C(n)(2n) - C(n+1)(2n),另一种形式则为:C(n)(2n)/(n+1),求和便是Catalan递归式

##代码实现

  • Java实现
import java.math.BigInteger;
// 打印前 n 个卡特兰数
int n = 20;
BigInteger ans = BigInteger.valueOf(1);
System.out.println("1:" + ans.toString());
BigInteger four = BigInteger.valueOf(4); 
BigInteger one = BigInteger.valueOf(1);
BigInteger two = BigInteger.valueOf(2);
for (int i = 2; i <= n; i++) {
    BigInteger bi = BigInteger.valueOf(i);
    ans = ans.multiply(four.multiply(bi).subtract(two)).divide(bi.add(one));
    System.out.println(i + ":" + ans.toString());
}
  • Python实现
# 打印前 n 个卡特兰数
ans, n = 1, 20
print("1:" + str(ans))
for i in range(2, n + 1):
    ans = ans * (4 * i - 2) // (i + 1)
    print(str(i) + ":" + str(ans))

##其他问题

1.二叉树

  • n + 1 个叶子节点能够构成多少种形状不同的(国际)满二叉树?
  • (国际)满二叉树定义:如果一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。
  • 思路
    • 使用深度优先搜索这个满二叉树,向左扩展时标记为 +1,向右扩展时标记为 -1。

2. 括号问题

  • n 对括号,则有多少种 “括号匹配” 的括号序列?
  • ( ( )( ) )···
  • 这个完全可直接转化为出入栈问题

3. 排队问题

  • 8 个高矮不同的人需要排成两队,每队 4 个人。其中,每排都是从低到高排列,且第二排的第 i 个人比第一排中第 i 个人高,则有多少种排队方式?

#总结

  • 基本上所有的卡特兰数问题经过一定的转换都可以还原成进出栈问题。因此,只要我们能够学会进出栈问题的解法,无论问题再怎么变化,本质还是不变的。
  • 卡特兰数问题中都会存在一种匹配关系,如进出栈匹配,括号匹配等,一旦计数问题中存在这种关系,那我们就需要去考虑这是否是卡特兰数问题。此外,我们还可以记住序列前四项:1, 1, 2, 5,这些将有利于我们联想到卡特兰数。
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
卡特兰数是一种组合数学中的数列,常用于计数一些具有特定结构的对象。其中,n个节点的二叉树个数也可以用卡特兰数进行计算。 首先,我们定义一个二叉树BST(n),其中n表示二叉树中节点的个数。我们可以发现,对于BST(0)来说,它是一棵空树,也是唯一的一种情况。对于BST(1),只有一个根节点,也是唯一的一种情况。对于BST(2),我们可以将根节点视为i,左子树中节点个数为i-1,右子树中节点个数为n-i,其中1<=i<=n。这样,我们可以通过递归的方式,将BST(n)的计算问题划分为求解BST(i-1)和BST(n-i)的计算问题,然后将二者的计算结果相乘,再将所有i从1到n的计算结果累加,即可得到BST(n)的结果。 这个过程可以表示为如下的递推公式: BST(0) = 1 BST(1) = 1 BST(n) = BST(0)*BST(n-1) + BST(1)*BST(n-2) + ... + BST(n-1)*BST(0),其中n>=2 通过计算我们可以发现,n个节点的二叉树个数恰好对应了卡特兰数C(n)的结果。因此,n个节点的二叉树个数是卡特兰数。这是因为卡特兰数的定义与BST(n)的递归公式相吻合,并且满足初始条件。卡特兰数是一种具有递归性质的数列,它在计算组合问题中经常出现。 总结起来,n个节点的二叉树个数是卡特兰数,是通过递推公式BST(n) = BST(0)*BST(n-1) + BST(1)*BST(n-2) + ... + BST(n-1)*BST(0)以及初始条件得到的。卡特兰数的计算结果能够准确表示n个节点二叉树的个数,这与二叉树的特定结构有关。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值