【python2C】算法基础:计时比较

不断改进代码,是学习进步必经之路。
判断代码优劣,在空间允许的情况下,计时就是最可靠的标尺。

打表不算,人脑不算

1.对于答案较为固定的题,预先算出所有可能的答案表,然后对应输入查找答案,从而获得一个满足题目要求速度的AC,当然不能算是出题者想要的解决方案。
2.用过于复杂的逻辑、或者较多较难的数理推导,过于依赖人脑代替电脑,显然是失去了算法竞赛的初衷,也可能耗费过多的比赛时间。(天才随意)

计时:时间戳之差

通过获取运行时的时间戳,对比一段代码运行前后的时间戳之差,来作为运行效率的比较。
但时间戳可能受各种条件影响而产生波动。
一般通过增加数据规模,或者多次重复累加/平均,来获得一个较为稳定可靠的值。

获取时间戳,python最常用的是time模块的 time.time() 和time.perf_counter()方法
c++的ctime也有类似的方法,但更可靠的是 chrono的chrono::high_resolution_clock::now()方法

python的time类

可以通过 dir(time)和help()自行查询学习
获取时间戳的方法主要有两类:(‌纪元时间Epoch指的是1970年1月1日00:00:00 UTC。)

python time类的时间戳方法
方法说明示例sleep
time返回以秒为单位的浮点数,从Epoch至今.time() -> float
perf_counter返回以秒为单位的浮点数,基准时间,最高精度.perf_counter() -> float
monotonic返回以秒为单位的浮点数,精度稍差但单调递增.monotonic() -> float
process_time返回当前进程的系统和用户CPU时间总和的秒数.process_time() -> float
X_ns返回以纳秒为单位的整数,X对应以上各种time.time_ns() -> int/

其他方法还包括thread_time()用于同一线程之间的时差计算。

此外,还有timeit模块用于重复测试

C++的chrono库

python本来就是用c++写的,所以对应的方法必须都有。
详细的见  chrono全面解析 和 超级水文std::chrono库使用说明

简单一句话,测试csp赛的代码,

首选用 std::chrono::high_resolution_clock::now(),如果不正常就换std::chrono::steady_clock

# include <bits/stdc++.h>
using namespace std;
using namespace std::chrono;

// ...

auto start=high_resolution_clock::now(); 

// do sth.

auto end=high_resolution_clock::now();
auto dt=duration_cast<milliseconds>(end - start).count();

cout<<dt<<" ms"<<endl;

一个例子

比较两种获得不超过n位的回文数的算法

# include <bits/stdc++.h>
using namespace std;
using namespace std::chrono;

int mkHW(int HW[],int n){//获得n位的奇回文数
	auto start=high_resolution_clock::now(); 
	// n为奇数,且首末位为奇数
	int cnt=0;
	long long d[(n+1)/2]={1}; if(n>=3)d[1]=101; 
	for(int i=2;i<(n+1)/2;i++)d[i]=d[i-1]*100-99;
	// 1位 
	for(int i=1;i<10;i+=2)	HW[cnt++]=i; if(n==1) return cnt;
	// 2位 
	HW[cnt++]=11; if(n==2) return cnt;
	// 3位 
	int iS=cnt,j=1; //iS预存3位的起点 
	for(int i=1;i<10;i+=2)for(int k=0;k<10;k++)HW[cnt++]=i*d[j]+k*10; 
	if(n==3) return cnt;
	// 5位
	int iE=cnt;j=2; //iE预存3位的终点,不包括iE
	for(int i=1;i<10;i+=2)
		for(int k=0;k<10;k++)
			for(int ii=iS;ii<(iE-iS)/5+iS;ii++) 
				HW[cnt++]=i*d[j]+k*d[j-1]*10+(HW[ii]%d[j-1])*10;
	if(n==5) return cnt;
	// n位
	for(j=3;j<(n+1)/2;j++){
		iS=iE;iE=cnt; // 上上轮的终点即为上轮的起点
		for(int i=1;i<10;i+=2)
			for(int k=0;k<10;k++)
				for(int ii=iS;ii<(iE-iS)/5+iS;ii++)  
					HW[cnt++]=i*d[j]+k*d[j-1]*10+(HW[ii]%d[j-1])*10;
	}
//	for(int i=0;i<cnt;i++)cout<<HW[i]<<" "; cout<<endl;
	auto end=high_resolution_clock::now();
	auto dt=duration_cast<nanoseconds>(end - start).count();
	cout<<"in func mkHW : "<<dt<<" ns"<<endl;
	return cnt;
}
bool isHW(int x){
    int x2=x,y=0; 
    while(x2!=0){y=y*10+x2%10;x2/=10;} 
    return x==y;
} 
int main(){
//	int n;cin>>n;
//	long long m; m=pow(10,n);
	int m=9999999,sq=int(sqrt(m*100))+1;
	int hw[sq];cout<<sq<<endl;
	
	// test for all 回文 
	auto start=high_resolution_clock::now(); 
	int cnt=0;for(int i=0;i<m;i++)if(isHW(i))hw[cnt++]=i;
	auto end=high_resolution_clock::now();
	auto d=duration_cast<nanoseconds>(end - start).count();
	cout<<"test 1 : "<<cnt<<" : "<<d<<" ns"<<endl;
//	for(int i=0;i<cnt;i++)cout<<hw[i]<<" "; cout<<endl;
	
	// test for odd 奇数回文 
	start=high_resolution_clock::now(); 
	cnt=0;
	int x2=m,n=0;while(x2!=0){n++;x2/=10;}
	cnt=mkHW(hw,n); 
	end=high_resolution_clock::now();
	d=duration_cast<nanoseconds>(end - start).count();
	cout<<"test 2 : "<<cnt<<" : "<<d<<" ns"<<endl; 
//	for(int i=0;i<cnt;i++)cout<<hw[i]<<" "; cout<<endl;

 	return 0;
}

明显可以看到创造比暴力遍历耗时要少很多,当然也麻烦些。(遍历比通过字符串转换快,那个根本运行不了这数据规模)

需要说明的是,有时测试时间会为0,后来发现是内存溢出或者其他各种隐形的错误的影响,修正后就正常了。
但上例中我这个函数mkHW里的计时器,为什么一直都是0呢?
steady_clock 代替 high_resolution_clock 就正常了。因为high_resolution_clock实际上是跟系统走的,在system_clock和steady_clock中选一个,本例中它选了前者,在函数调用时不稳定。改了后者就正常了。

========
期待评论里留言或者私信交流!

  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jikuibu2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值