一、实验名称
半数集问题;
问题描述:
给定一个自然数 n,由 n 开始可以依次产生半数集 set(n)中的数如下。
(1) n∈set(n);
(2) 在 n 的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
(3) 按此规则进行处理,直到不能再添加自然数为止。
注意半数集是多重集。
对于给定的自然数 n,编程计算半数集 set(n)中的元素个数。
二、实验目的
通过上机实验,要求掌握半数集问题的问题描述、算法设计思想、程序设
计。
三、实验原理
解决半数集问题,并计算出程序运行所需要的时间。
四、实验步骤
以题中给出的例子,例如,set(6)={6,16,26,126,36,136}。半数集 set(6)中有 6 个元素。
第一次半数集 | 16 | 26 | 36 |
忽略原始数字6 | 1 | 2 | 3 |
12 | 13 |
由图可知,6的半数集依赖于1至3的半数集。
1(本身)+1的半数集个数+2的+…+3的半数集个数=6的半数集个数。
而每个子半数集又依赖于它们的子半数集,则可利用递归调用实现算法。
得出一个公式:
五、关键代码
1.时间复杂度较高的版本
int comp(int n){
int ans=1; //定义变量 初始为1,为该数自身;
if(n>1){ //若求1的半数集 则就是1,若大于1 则执行;
for(int i=1;i<=n/2;i++) //循环从1 加到 n/2 半数集
ans+=comp(i);
}
return ans; //返回半数集个数
}
2.记忆式搜索
int a[2000]; //定义一个数组,来存储对应半数集个数
int comp(int n){
int ans=1; //定义变量 初始为1,为该数自身;
if(a[n]>0) return a[n]; //如果a[n]>0,证明n的半数集已经被算过,保存到了数组a[n]里,直接返回;
for(int i=1;i<=n/2;i++) //循环从1 加到 n/2 半数集
ans+=comp(i);
a[n]=ans; //如果n的半数集没被算过,现在算过了,保存到a[n]中,返回ans或者a[n]都行。
return ans; //返回半数集个数
}
3.自己生成测试数据(放弃版)
//生成规模为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();
六、测试结果
1.第一种测试数据生成:
到三位数、四位数时,就会出错
2.第二种测试数据生成:(遍历)
时间复杂度:O(2^n)
对于每个 n,comp 函数会递归地调用 comp(i),其中 i 从 1 到 n/2。这导致了一个递归的树状结构。对于每个递归调用,都会对 ans 进行累加。因此,总的递归调用次数是指数级的。
在递归调用的过程中,如果 a[n] > 0,则直接返回 a[n],这是为了避免重复计算。
因此,该程序的时间复杂度是指数级的,具体来说是 O(2^n)。这是因为对于每个 n,都会有指数级别的递归调用。
七、实验心得
通过这次实验,我了解熟悉了半数集问题的求解过程及原理。对于自己实现的案例,感觉存在误差,画出来的图不符合直觉,也可能是求解运行时间的程序有问题。
八、完整代码
#include <iostream>
#include <fstream>
#include <windows.h>
#include <time.h>
#include <vector>
#include <cmath>
#include <queue>
using namespace std;
/*
int comp(int n){
int ans=1; //定义变量 初始为1,为该数自身;
if(n>1){ //若求1的半数集 则就是1,若大于1 则执行;
for(int i=1;i<=n/2;i++) //循环从1 加到 n/2 半数集
ans+=comp(i);
}
return ans; //返回半数集个数
}
*/
/*
以上的代码实现有一些重复的计算,例如算12的半数集时已经算过了1-6的半数集,
而算6的半数集时算了1-3的半数集,1-3的半数集重复了多次。
*/
//记忆式搜索
long long a[2000]; //定义一个数组,来存储对应半数集个数
long long comp(int n){
long long ans=1; //定义变量 初始为1,为该数自身;
if(a[n]>0) return a[n]; //如果a[n]>0,证明n的半数集已经被算过,保存到了数组a[n]里,直接返回;
for(int i=1;i<=n/2;i++) //循环从1 加到 n/2 半数集
ans+=comp(i);
a[n]=ans; //如果n的半数集没被算过,现在算过了,保存到a[n]中,返回ans或者a[n]都行。
return ans; //返回半数集个数
}
int main(int argc, char** argv) {
ofstream out1("output.txt");
int x=1;
while(x<=1500){
// //生成规模为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;
ifstream in("input1.txt");
int n;
in>>n;
int cishu=1000;
long long result;
while(cishu--){
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nBegin);
result=comp(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\\3-半数集问题\\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()