HNU_算法_实验2(2021级)-算法实验2-集合划分问题

   一、实验名称

集合划分问题;

给定正整数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()
在C语言中,实现集合划分问题通常涉及到动态规划或者回溯法。集合划分问题的目标是将一个集合分割成若干个互不相交的部分,每个部分满足特定条件。例如,著名的“割牛”问题(Partition Problem),就是寻找一组数能否分成两组,使得两组的和相等。 下面是一个简单的示例,演示如何使用回溯法解决"割牛"问题: ```c #include <stdio.h> #include <stdbool.h> bool is_partition(int[] arr, int n, int target) { int total = 0; for (int i = 0; i < n; ++i) total += arr[i]; if (total % 2 != 0) return false; int sum = total / 2; bool dp[n + 1][sum + 1]; memset(dp, false, sizeof(dp)); dp[0][0] = true; for (int i = 0; i <= n; ++i) { for (int j = 0; j <= sum; ++j) { if (arr[i - 1] <= j && i > 0) { dp[i][j] |= dp[i - 1][j - arr[i - 1]]; } dp[i][j] |= (i == n && j == sum); } } return dp[n][sum]; } void partition(int[] arr, int n) { if (is_partition(arr, n, arr[0])) { printf("集合可以分为两部分,总和相等.\n"); } else { printf("集合无法分为两部分,总和相等.\n"); } } int main() { int arr[] = {1, 5, 7, 2, 8}; int n = sizeof(arr) / sizeof(arr[0]); partition(arr, n); return 0; } ``` 这个程序首先检查总数是否偶数,如果不是,则返回false。然后,使用动态规划数组`dp`记录前i个元素是否可以形成目标和。最后,根据`dp`数组的结果判断是否存在符合条件的划分。 注意,这只是一个基础版本,实际应用中可能需要更复杂的条件判断和优化。此外,对于大型集合,这种方法可能会比较低效,因为它需要遍历所有可能性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值