一、实验名称
集合划分问题;
给定正整数n(1<=n<=20),计算出n个元素的集合{1,2,...,n} 可以划分为多少个不同的非空子集。
二、实验目的
通过上机实验,要求掌握集合划分问题的问题描述、算法设计思想、程序设计。
三、实验原理
解决集合划分问题,并计算出程序运行所需要的时间。
四、实验步骤
以集合元素个数为4为例,我们来看应该怎么求解集合划分问题。因为我们采用的是分治策略,考虑3个元素的集合,可划分为
(1) 1个子集的集合:{{1,2,3}}
(2) 2个子集的集合:{{1,2},{3}},{{1,3},{2}},{{2,3},{1}}
(3) 3个子集的集合:{{1},{2},{3}}
∴F(3,1)=1;F(3,2)=3;F(3,3)=1;因此,我们先看集合元素个数为3的集合的划分:
如果我们要求4个元素的集合划分为2个子集的集合的个数F(4,2),求解过程如下:
我们可以在(2)中加入4,加入4的方式有6种,
{{1,2,4},{3}},{{1,2},{3,4}},
{{1,3,4},{2}},{{1,3},{2,4}},
{{2,3,4},{1}},{{2,3},{1,4}}
还可以在(1)中加入4,只有一种方式:
{{1,2,3},{4}}
所以F(4,2)=2*F(3,2)+F(3,1)
综上,集合划分的公式如下:
F(n,m)=F(n −1,m −1)+m ∗F(n −1,m)
这就是组合数学里面的贝尔数:
五、关键代码
1.计算集合划分个数
long long Division(long long n,long long m){
if(n==m || m==1) return 1;
return m*Division(n-1,m)+Division(n-1,m-1);
}
long long fun(int n){
long long ans=0;
for(int i=1;i<=n;i++){
ans+=Division(n,i);
}
return ans;
}
2.动态规划的解法
使用动态规划来计算贝尔数的方法,时间复杂度通常是O(n^2 )。这是因为在动态规划的过程中,需要填充一个二维表格,表格的大小为n×n。
long long bellNumber(int n) {
vector<vector<long long >> bell(n + 1, vector<long long >(n + 1, 0));
bell[0][0] = 1;
for (int i = 1; i <= n; ++i) {
bell[i][0] = bell[i - 1][i - 1];
for (int j = 1; j <= i; ++j) {
bell[i][j] = bell[i - 1][j - 1] + bell[i][j - 1];
}
}
return bell[n][0];
}
3.自己创建测试数据
因为复杂度为指数,所以一旦n比较大,输出结果就会溢出,所以直接用一个循环遍历就行。而且题目中要求,n是在0-20中。
while(x<21){
// //生成规模为n的随机数
// cout<<"请输入数据规模n:"<<endl;
// int n,c;
// cin>>n;
// ofstream out("input1.txt");
// // out<<n<<'\n';
// srand((unsigned)time(NULL));
// // x [a,b]
// int a=pow(10,n-1),b=pow(10,n);
// out<< (rand() % (b-a+1))+ a<<'\n';
// out.close();
//
//生成测试数据
ofstream out("input1.txt");
out<<x++<<'\n';
out.close();
}
六、测试结果
时间复杂度: O(2^n)
递归计算贝尔数的时间复杂度通常是指数级的,即 O(2^n )。因为递归过程中存在大量的重复计算,导致指数级的时间复杂度。
每个样例运行1000次取平均值:
动态规划求解:
七、实验心得
通过这次实验,我了解熟悉了集合划分问题的求解过程及原理。对于自己实现的案例,感觉存在误差,画出来的图不符合直觉,也可能是求解运行时间的程序有问题。
八、完整代码
#include <iostream>
#include <fstream>
#include <windows.h>
#include <time.h>
#include <vector>
#include <cmath>
using namespace std;
long long Division(long long n,long long m){
if(n==m || m==1) return 1;
return m*Division(n-1,m)+Division(n-1,m-1);
}
long long fun(int n){
long long ans=0;
for(int i=1;i<=n;i++){
ans+=Division(n,i);
}
return ans;
}
//
//long long bellNumber(int n) {
// vector<vector<long long >> bell(n + 1, vector<long long >(n + 1, 0));
//
// bell[0][0] = 1;
//
// for (int i = 1; i <= n; ++i) {
// bell[i][0] = bell[i - 1][i - 1];
// for (int j = 1; j <= i; ++j) {
// bell[i][j] = bell[i - 1][j - 1] + bell[i][j - 1];
// }
// }
//
// return bell[n][0];
//}
int main(int argc, char** argv) {
ofstream out1("output.txt");
int x=1;
while(x<21){
// //生成规模为n的随机数
// cout<<"请输入数据规模n:"<<endl;
// int n,c;
// cin>>n;
// ofstream out("input1.txt");
// // out<<n<<'\n';
// srand((unsigned)time(NULL));
// // x [a,b]
// int a=pow(10,n-1),b=pow(10,n);
// out<< (rand() % (b-a+1))+ a<<'\n';
// out.close();
//
//生成测试数据
ofstream out("input1.txt");
out<<x++<<'\n';
out.close();
int i,maxi,mini;
LARGE_INTEGER nFreq,nBegin,nEnd;
double time=0;
ifstream in("input1.txt");
int n;
in>>n;
int cishu=1000;
long long result;
while(cishu--){
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nBegin);
result=fun(n);
// result=bellNumber(n);
QueryPerformanceCounter(&nEnd);
time+=(double)(nEnd.QuadPart-nBegin.QuadPart)/(double)nFreq.QuadPart;
}
time=time/1000;
cout<<"测试数据为:"<<n<<endl;
cout<<"结果:"<<result<<"\n查询时间:"<<time<<endl<<endl;
out1<<n<<' '<<time<<endl;
in.close();
}
out1.close();
return 0;
}
九、绘图代码
import matplotlib.pyplot as plt
# 读取txt文件,假设文件名为data.txt
file_path = 'F:\\3-CourseMaterials\\3-1\\3-算法设计与分析\实验\lab2\\1-code\\4-集合划分问题\\output.txt'
# 存储x和y的列表
x_values = []
y_values = []
# 读取文件并提取数据
with open(file_path, 'r') as file:
for line in file:
# 假设数据以空格或逗号分隔
x, y = map(float, line.strip().split())
x_values.append(x)
y_values.append(y)
print(x_values)
print(y_values)
# 绘制图形
plt.plot(x_values, y_values)
plt.title('X vs Y Plot')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.grid(True)
plt.show()