集合划分问题

问题描述:

n个元素的集合{1,2,.,n }可以划分为若干个非空子集。例如,当n=4 时,集合{1,2,3,4}可以划分为15个不同的非空子集如下:

{1}{2}{3}{4}}{{12}{3}{4}}{{13}{2}{4}}{{14}{2}{3}}{{23}{1}{4}}{{24}{1}{3}}{{34}{1}{2}}{{12}{34}}{{13}{24}}{{14}{23}}{{123}{4}}{{124}{3}}{{134}{2}}{{234}{1}}{{1234}}

其中,集合{{1,2,3,4}} 由1个子集组成;集合{{1,2},{3,4}},{{1,3},{2,4}},{{1,4},{2,3}},{{1,2,3},{4}},{{1,2,4},{3}},{{1,3,4},{2}},{{2,3,4},{1}} 由2个子集组成;集合{{1,2},{3},{4}},{{1,3},{2},{4}},{{1,4}.{2},{3}},{{2,3},{1},{4}},{{2,4},{1},{3}},{{3,4},{1},{2}} 由3 个子集组成;集合{{1},{2},{3},{4}} 由4个子集组成。

编程任务:

给定正整数n 和m,计算出n 个元素的集合{1,2,., n }可以划分为多少个不同的由m 个
非空子集组成的集合。
数据输入:
由文件input.txt 提供输入数据。文件的第1 行是元素个数n 和非空子集数m。
结果输出:
程序运行结束时,将计算出的不同的由m个非空子集组成的集合数输出到文件output.txt中。

输入文件示例                                        输出文件示例
4 3                                                 6

算法思想

这是一个很简单的分治题,我们可以定义一个划分函数setSplit(n,m),其中n表示划分集合规模,m表示要划分的子集数目,我们可以这样处理,从第n个数开始往1推导,对于第i个数i,有2种处理方法:

  • i 可以进入m个集合中的任何一个:m*setSplit(n-1,m)
  • i 可以独自成为一个单数集:setSplit(n-1,m-1)
  • 当n=1或者m=n时只存在一种划分
  • 当n<m时划分数为0
    从而得到递推关系式
  • 另外由于计算过程中会有重复的计算,所以我们用记忆型递推关系,将setSplit(n,m)存放在数组中,从而避免重复计算,提升算法效率
    代码如下:
#include <iostream>
using namespace std;
const int maxn=1005;
int table[maxn][maxn]={0};
int setSplit(int n,int m)
{
    if(n<m) return 0;
    if(n==m||m==1) return 1;
    if(!table[n][m]) table[n][m]=m*setSplit(n-1,m)+setSplit(n-1,m-1);
    //加入任意一个集合或者独立成一个集合
    return table[n][m];
}
int main()
{
    int n,m;
    cin>>n>>m;
    cout<<setSplit(n,m)<<endl;
    return 0;
}

算法复杂度分析

(1)由于递推过程中我们从最底层开始推导,所以算法最终会将一个mn的表格填满,算法复杂度为O(mn)

原创文章 236 获赞 430 访问量 7万+

猜你喜欢

转载自blog.csdn.net/weixin_44307065/article/details/105167906