Catalan数
中文:卡特兰数
原理:
令h(1)=1,h(0)=1,catalan数满足递归式:
h(n)= h(1)*h(n-1) + h(2)*h(n-2) + ... + h(n-1)h(1) (其中n>=2)
另类递归式:
h(n)=((4*n-2)/(n+1))*h(n-1);
该递推关系的解为:
h(n+1)=C(2n,n)/(n+1) (n=1,2,3,...)
我并不关心其解是怎么求出来的,我只想知道怎么用catalan数分析问题。
我总结了一下,最典型的四类应用:(实质上却都一样,无非是递归等式的应用,就看你能不能分解问题写出递归式了)
1.括号化问题。
矩阵链乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?(h(n)种)
2.出栈次序问题。
一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列?
类似:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
3.将多边行划分为三角形问题。
将一个凸多边形区域分成三角形区域的方法数?
类似:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果她
从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?
4.给顶节点组成二叉树的问题。
给定N个节点,能构成多少种不同的二叉树?
(能构成h(N)个)
Catalan数的解法
Catalan数的组合公式为 Cn=C(2n,n) / (n+1);
此数的递归公式为 h(n ) = h(n-1)*(4*n-2) / (n+1)
/* 大数解
对于大数来说,就应该使用下面的大数算法。
使用的公式为:h(n) = h(n-1)*(4*n-2)/(n+1);
*/
// 0ms
#include<iostream>
using
namespace std;
#define MAX 100
#define BASE 10000
void multiply(
int a[],
int Max,
int b)
//
大数乘法,注意参数的传递
{
int i,array=
0;
for (i = Max-
1; i >=
0; i--)
{
array += b * a[i];
a[i] = array % BASE;
//
数组每一位存放大数的四位数字
array /= BASE;
}
}
void divide(
int a[],
int Max,
int b)
//
模拟大数除法
{
int i, div =
0;
for (i =
0; i < Max; i++)
{
div = div * BASE + a[i];
a[i] = div / b;
div %= b;
}
}
int main()
{
int a[
101][MAX],i, n;
memset(a[
1],
0,MAX*
sizeof(
int));
for (i=
2, a[
1][MAX-
1] =
1; i <
101; i++)
//
高坐标存放大数低位
{
memcpy(a[i], a[i-
1], MAX *
sizeof(
int));
//
h[i] = h[i-1];
multiply(a[i], MAX,
4 * i -
2);
//
h[i] *= (4*i-2);
divide(a[i], MAX, i +
1);
//
h[i] /= (i+1);
}
while (cin >> n)
{
for (i =
0; i < MAX && a[n][i] ==
0; i++);
//
去掉数组前为0的数字。
cout << a[n][i++];
//
输出第一个非0数
for (; i < MAX; i++)
{
printf(
"
%04d
",a[n][i]);
//
输出后面的数,并每位都保持4位长度!(32767)
}
cout << endl;
}
return
0;
}
AC CODE 2:
//C(0) = 1 ; (n+2)*C(n+1) = (4n+2)*C(n); 也即是:h(n) = h(n-1) * (4 * n - 2)/(n+1);
// 0MS
#include<iostream>
using
namespace std;
int a[
101][
101] = {
0};
int main()
{
int n,i,j,len,r,temp,t;
int b[
101];
a[
1][
0] =
1;
//
低坐标存放大数的低位
len =
1;
b[
1] =
1;
for (i =
2; i <=
100; i++)
{
t = i -
1;
for (j=
0;j<len;j++)
//
模拟乘法,从低位开始
{
a[i][j] = a[i-
1][j] * (
4 * t +
2);
}
for (r = j =
0; j < len; j++)
//
处理相乘结果
{
temp = a[i][j] + r;
a[i][j] = temp %
10;
r = temp /
10;
}
while (r)
//
进位处理
{
a[i][len++] = r %
10;
r /=
10;
}
for (j = len-
1, r =
0; j >=
0; j--)
//
模拟除法,从高位开始
{
temp = r *
10 + a[i][j];
a[i][j] = temp / (t+
2);
r = temp % (t+
2);
}
while (!a[i][len-
1])
//
高位零处理
{
len--;
}
b[i] = len;
//
记录结果的长度
}
while (cin >> n)
{
for(j = b[n] -
1; j >=
0; j--)
{
printf(
"
%d
",a[n][j]);
}
printf(
"
\n
");
}
return
0;
《编程之美》中提到了“买票找零”问题,查阅了下资料,此问题和卡特兰数 Cn有关,其定义如下:
卡特兰数真是一个神奇的数字,很多组合问题的数量都和它有关系,例如:
- Cn= 长度为 2n的 Dyck words的数量。 Dyck words是由 n个 X和 n个 Y组成的字符串,并且从左往右数, Y的数量不超过 X,例如长度为 6的 Dyck words为:
XXXYYY XYXXYY XYXYXY XXYYXY XXYXYY
- Cn= n对括号正确匹配组成的字符串数,例如 3对括号能够组成:
((())) ()(()) ()()() (())() (()())
- Cn= n+1个数相乘,所有的括号方案数。例如, 4个数相乘的括号方案为:
((ab)c)d (a(bc))d (ab)(cd) a((bc)d) a(b(cd))
- Cn= 拥有 n+1 个叶子节点的二叉树的数量。例如 4个叶子节点的所有二叉树形态:
- Cn=n*n的方格地图中,从一个角到另外一个角,不跨越对角线的路径数,例如, 4×4方格地图中的路径有:
- Cn= n+2条边的多边形,能被分割成三角形的方案数,例如 6边型的分割方案有:
- Cn= 圆桌周围有 2n个人,他们两两握手,但没有交叉的方案数。
在《Enumerative Combinatorics》一书中,竟然提到了多达 66种组合问题和卡特兰数有关。
分析
“卡特兰数”除了可以使用公式计算,也可以采用“分级排列法”来求解。以 n对括弧的合法匹配为例,对于一个序列 (()而言,有两个左括弧,和一个右括弧,可以看成“抵消了一对括弧,还剩下一个左括弧等待抵消”,那么说明还可以在末尾增加一个右括弧,或者一个左括弧,没有左括弧剩余的时候,不能添加右括弧。
由此,问题可以理解为,总共 2n个括弧,求 1~2n级的情况,第 i 级保存所有剩余 i 个左括号的排列方案数。 1~8级的计算过程如下表:
计算过程解释如下: 1级:只能放 1个“(”; 2级:可以在一级末尾增加一个“)”或者一个“ (”
以后每级计算时,如果遇到剩余 n>0个“(”的方案,可以在末尾增加一个“ (”或者“ )”进入下一级;遇到剩余 n=0个“(”的方案,可以在末尾增加一个“ (”进入下一级。
奇数级只能包含剩余奇数个“(”的排列方案
偶数级只能包含剩余偶数个“(”的排列方案
从表中可以看出,灰色部分可以不用计算。
解法
关键代码为:
double Catalan(
int n)
{
if (n ==
0)
return
1;
for (
int i =
2; i <=
2 * n; i++)
{
var m = i <= n ? i :
2 * n +
1 - i;
for (
int j = (i -
1) &
1; j <= m; j +=
2)
{
if (j >
0) arr[j -
1] += arr[j];
if (j < n) arr[j +
1] += arr[j];
arr[j] =
0;
}
}
return arr[
0];
}
其中:
n为 Cn中的 n;
arr = new double[n + 1];//arr[i]代表有 k个括弧的时候,剩余 "("个数为 i的排列方案个数 arr[1] = 1;
讨论
算法复杂度为 = O(n^2),空间复杂度为 O(n+1)。相对于利用公式计算而言,此方法的优势在于——没有乘除法,只有加法。
转载自:
http://blog.163.com/lz_666888/blog/static/1147857262009914112922803/