HNU_算法_实验2(2021级)-算法实验1-半数集问题

一、实验名称

半数集问题;

问题描述:

给定一个自然数 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()
  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值