Set Partition Algorithm(集合划分算法)

转载自: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;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值