Catalan是一个组合数列,即一组情景下排列的组合数,前k次操作,后k个操作,前k次操作必须在后k次操作之前完成,注2k次操作可相互交叉。
递推式:f(n+1)=∑f(i)*f(n-i) ,i∈[0,n]
即:f(n)=∑f(i)*f(n-1-i) ,i∈[0,n-1]
f(0)=1;
n = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
f(n)=1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, …
仅根据递推式来计算第n项的Catalan数较简单,只要一个递归操作即可,但是可发现递推式中包含重复计算项,因此根据DP思想,可通过memorize方法存储已经计算得到的项,即利用一个向量vector<int>memorize存储n个项的值,那么如何判断一个值是否已被计算呢?最简单的方法就是初始化memorize的值与所有正确答案的值都不相同,即当正确答案都为正数时,初始化memorize的值为负数,同时在递归中通过别名进行传递。
注意,在编程的时传进来的n是递推式中的n+1项,所以子递归时为n-1-i,一定要注意,在子递归时,传进去的数要越来越小,不然会形成死循环递归,因为递归在原地踏步,而没有收敛。不收敛的递归无法递归到终止项,但也可根据实际需求功能强行设定终止条件。
本文的具体算法实现如下:
int DynamicProgramming::catalanNumber(int n)
{
//------------通过DP算法保存中间重复结果----------------
std::vector<int> f; //fi为中间值
f.resize(n+1);
for (int i = 0; i <= n;i++)
{
f[i] = -1;
}
int fn = conquerCatalan(f, n);
return fn;
}
int DynamicProgramming::conquerCatalan(std::vector<int> &f, int n)
{
if (f[n]!=-1)
{
return f[n];
}
int result = 0;
if (n == 0)
{
result = 1;
}
else
{
for (int i = 0; i <= n - 1; i++)
{
result += conquerCatalan(f, i)*conquerCatalan(f, n - 1 - i); //fn==f(i)或者f(n-i)
}
}
f[n] = result;
return result;
}
总结:如果单就Catalan数列本身仅仅是一串数列,但这个数的第n项是一些实际应用问题中出现的排列组合,比如n个不同数进栈出栈的全排列问题等等,该类问题的一个共性特点是总共有2n次操作,2k∈2n次操作,前k次操作必须不小于后k次操作,前后k次操作可相互交叉。像这类问题就可直接根据Catalan递推式求解,那为什么这类问题可用Catalan递推式计算呢?应该是这类实际的问题在数学上归纳成了Catalan数列?