Bell数等价于第二类斯特林数,主要用于求集合划分问题:求将1到n个数划分成子集的排列组合数量,且子集之间无交集。
根据Stirling组合原理可递推出Bell数的递推表达式,即从后往前推:假设已知第n-1个Bell数,那么第n个Bell数就是将第n个数插入到前面排好列的n-1个数的m个子集中;或者第n个数独立成一个堆,与前面将n-1个数划分成m-1堆共同构成n个数划分m个集合。因此:递推式为:B(n,m)=m*B(n-1,m)+B(n-1,m-1) 其中m*B(n-1,m)是插入,B(n-1,m-1)是单独成1堆合并到总堆数m里,且B(0)=1,B(1)=1,B(2)=2;
特注:第n个Bell数为n个数可划分成1-n个集合。第i个Bell数与B(i,j)相区别,B(i,j)是将i个数划分成j个集合的组合数量,但是,请注意,第i个Bell数的含义是将i个数划分成1到i个集合的所有不同组合数量,所以要求第n个Bell数,就要将n个数从划分到1个集合一直到划分到n个集合的组合数,即用一个for循环来累加m=1,2,3,...n的划分数,第n个Bell数的递推式为:f(n)=∑B(n,i),i=1,2,3,...n。另外,由于B(n,m)表示将n个数划分成m个集合的排列个数,所以可用一个二维向量memorize来存储第i个数划分成第j个集合的组合排列,这样就可以利用DP思想来计算递推式,减少重复计算项由于B(n,m)的递推式中存在重复计算项),提高计算效率。
主要算法如下:
int DynamicProgramming::bellNumber(int n)
{
std::vector<std::vector<int>> memorize; //f为中间值
memorize.resize(n + 1);
for (int i = 0; i < memorize.size(); i++)
{
memorize[i].resize(n + 1);
}
for (int i = 0; i < memorize.size(); i++)
{
for (int j = 0; j < memorize[i].size(); j++)
{
memorize[i][j] = -1;
}
}
int fn = 0;
if (n == 1||n==0)
{
fn = 1;
}
for (int i = 1; i <= n;i++)
{
fn += i*conquerBell(memorize, n - 1, i) + conquerBell(memorize, n - 1, i - 1);
}
return fn;
}
int DynamicProgramming::conquerBell(std::vector<std::vector<int>> &memorize, int n, int m)
{
//1,1,2,5
int result=0;
if (memorize[n][m] != -1)
{
return memorize[n][m];
}
if (n < m)
{
return 0;
}
if (m==0)
{
return 0;
}
if (n == 1)
{
result = 1;
}
else
{
result += m*conquerBell(memorize, n - 1, m) + conquerBell(memorize, n - 1, m - 1);
}
memorize[n][m] = result;
return result;
}