转载自:http://blog.sina.com.cn/s/blog_567842410100nf0c.html
设n个元素的集合可以划分为f(n,m)个不同的由m个非空子集组成的集合。
考虑3个元素的集合,可划分为
① 1个子集的集合:{{1,2,3}}
② 2个子集的集合:{{1,2},{3}},{{1,3},{2}},{{2,3},{1}}
③ 3个子集的集合:{{1},{2},{3}}
∴f(3,1)=1;f(3,2)=3;f(3,3)=1;
如果要求f(4,2)该怎么办呢?
a.往①里添一个元素{4},得到{{1,2,3},{4}}
b.往②里的任意一个子集添一个4,得到
{{1,2,4},{3}},{{1,2},{3,4}},
{{1,3,4},{2}},{{1,3},{2,4}},
{{2,3,4},{1}},{{2,3},{1,4}}
∴f(4,2)=f(3,1)+2*f(3,2)=1+2*3=7
推广,得f(n,m)=f(n-1,m-1)+m*f(n-1,m).
f(n, m)的结果就是包括n个元素的集合进行m划分的所有可能划分数。对于边界条件,如果n<m或m<1或n<1,则无法进行划分,返回0;如果n=m,则产生该集合的最大划分n, 返回n; 如果m=1,则产生该集合的最小划分E,返回1。这个结果也就是第二类stirling数。
int partitioncount(std::vector<int> & v, int n, int m)
{
if(n<m || n<1 || m<1)
return 0;
if(m==1)
return 1;
return partitioncount(v, n-1, m-1) + m * partitioncount(v, n-1, m);
}
很多时候,我们不仅仅需要求出所有的划分数,我们还需要产生所有可能的划分,并将每个划分的元素列表打印出来。同样,根据上面的划分递归产生办法,我们可以直接用回溯的方式递归求解。第一条递归路径,先求出n-1个元素子集的所有m-1划分,然后将第n个元素作为一个单独的子集添加到这些划分中;第二条递归路径,求出n-1个元素的所有m划分,然后对每一个划分的所以子集,将第n个元素分别放入,产生m*f(n-1,m)个新划分。
用回朔来解划分问题的话,会产生大量的重复计算,因为很多f(n-x,m-y)会存在与不同的回溯路径中,根据经验,处理这种NP complete问题,很好的一个解决方案就是使用动态规划(dynamic programming)。那么动态规划如何来解这个问题呢?
个人理解,动态规划实际上是对回溯的一种改进,以空间换时间,既然在求每一个f(n,m)之前都必须求出f(n-1,m-1)和f(n-1,m),那么我们可以用一个n*m的数组来保存这些中间结果。在计算f(n-x,m-y)之前,我们先检查一下数组中是否有计算结果,如果有则直接返回,否则,进行计算。在这种思路下,实际上我们只需要O(n*m)就可以将结果计算出。
partitionlist Partition(std::vector<int> v, int n, int m)
{
partitionlist result;
if(n < m || n<1 || m<1)
return result;
if(m==1)
{
partitiontype cur;
cur.push_back(v);
result.push_back(cur);
return result;
}
partitionset tempset;
tempset.push_back(v.back());
v.pop_back();
partitionlist temp = Partition(v, n-1, m-1);
for(partitionlist::iterator iter=temp.begin(); iter!=temp.end(); iter++)
{
(*iter).push_back(tempset);
result.push_back(*iter);
}
temp = Partition(v, n-1, m);
for(partitionlist::iterator iter=temp.begin(); iter!=temp.end(); iter++)
{
for(int i=0; i<m; i++)
{
partitiontype temptype = (*iter);
temptype[i].push_back(tempset.front());
result.push_back(temptype);
}
}
return result;
}