当前位置: 首页 > news >正文

欧拉筛法寻找素数与计算欧拉函数求和

欧拉筛法寻找素数与计算欧拉函数求和

  • 一、欧拉函数
    • 1.1定义
    • 1.2性质
    • 1.3唯一分解定理(算术基本定理)
  • 二、Eratosthenes筛法寻找素数
  • 三、欧拉筛法寻找素数
    • 3.1算法代码
    • 3.2算法分析
      • 3.2.1时间复杂度分析(对合数进行不重复筛选)
      • 3.2.2算法正确性分析(对合数进行完全筛选)
    • 3.3与Eratosthenes法实际运行时间对比
  • 四、欧拉筛法计算欧拉函数求和
    • 4.1欧拉函数求和代码
    • 4.2代码改进
      • 4.2.1使用vector类型数组
      • 4.2.2优化数组使用
  • 参考文档

一、欧拉函数

1.1定义

1~n中与n互质的数的个数被称为欧拉函数(记作 phi[n]),例如1 ~ 6中与6互质的数有1,5,所以phi[6]=2。规定phi[1]=1。

1.2性质

  1. m = m 1 m 2 m=m_{1}m_{2} m=m1m2,若 m 1 m_{1} m1与m的所有素因数都相同,则phi[m]= m 2 m_{2} m2×phi[ m 1 m_{1} m1]
  2. m = m 1 m 2 m=m_{1}m_{2} m=m1m2,若 m 1 m_{1} m1 m 2 m_{2} m2互质,则phi[m]=phi[ m 1 m_{1} m1]×phi[ m 2 m_{2} m2]
  3. 若m为素数,则phi[m]=m-1

1.3唯一分解定理(算术基本定理)

设整数a≥2,则a一定可以表示为素数的乘积,即 a = p 1 p 2 . . . p s a=p_{1}p_{2}...p_{s} a=p1p2...ps p s p_{s} ps是素数。

二、Eratosthenes筛法寻找素数

Eratosthenes筛法寻找素数的原理及步骤为:

  1. 根据唯一分解定理可推论出:对于整数n,一定有素因数p< n \sqrt{n} n
  2. 首先筛选出1~ N \sqrt{N} N 范围内的素数,这些素数的整数倍一定是合数,除去这些合数外,剩下的就是1-N范围内的素数。

Eratosthenes筛法算法复杂度为O(NloglogN),代码如下:

unsigned eratosthenes(unsigned upL)
{
	unsigned isprime[upL + 1] = {0};
	unsigned m = sqrt(upL + 0.5);
	for(unsigned i = 2 ; i <= m; i++)
	{
		if(!isprime[i])
		{
			for(unsigned j = i * i; j <= upL; j+=i)
			{
				isprime[j] = 1;
			}
		}
	}
	return 0;
}

三、欧拉筛法寻找素数

3.1算法代码

欧拉筛法寻找素数算法复杂度为O(N),相较于Eratosthenes筛法时间复杂度大幅度降低,但是也更难理解,先给出代码如下:

unsigned euler(unsigned upL)
{
	unsigned n = 0;
	unsigned prime[upL + 1] = {0}, isprime[upL + 1] = {0};
	for(unsigned i = 2 ; i <= upL; i++)
	{
		if(!isprime[i]) prime[++n] = i;
		for(unsigned j = 1; j <= n && i * prime[j] <= upL; j++)
		{
			isprime[prime[j] * i] = 1;
			if(i % prime[j] == 0) break;
		}
	}
	return 0;
}

3.2算法分析

3.2.1时间复杂度分析(对合数进行不重复筛选)

欧拉筛法求解思路与Eratosthenes筛法类似,都为先筛选出素数与合数,然后根据欧拉函数性质求和

代码中筛选素数与合数的部分使用双重循环,但不会对合数进行重复筛选,此处关键代码为if(i % prime[j] == 0) break;,分析如下:

  1. 如果i除以prime[j]等于0,则说明i的其中一个素因数为prime[j],设i=prime[j] * a;
  2. 而此时若不跳出对j的循环继续执行,则j++,i * prime[j+1]=prime[j] * prime[j+1] * a,prime[j+1] * a大于i,在以后对i的循环中必会重复遇到此合数,所以在此时跳出循环,控制程序只对合数筛选一次;
  3. 举例说明,当i=4时,4%2==0时,如不跳出内层循环,则下一次内循环会标记4 * 3=12为合数,而之后当i=6时,6*2=12又会被重复标记。

3.2.2算法正确性分析(对合数进行完全筛选)

对合数筛选的过程中是否会漏掉合数,分析如下:

  1. 根据唯一分解定理,任一合数m必然可以表示为素因数乘积的形式,设其中最小的素因数为p,则m可以表示为m=p*q;
  2. q一定≥p,否则m的最小素因数就不是p,当i循环到q时,素因数p一定被添加至prime数组中,m被标记为合数。

3.3与Eratosthenes法实际运行时间对比

经过多次测试,Eratosthenes法约为1ms,而欧拉法约为2ms,运行截图如下:

在这里插入图片描述
在这里插入图片描述
实际与理论相悖的原因可能是:欧拉法在筛选合数的同时需要操作一个额外的存储素数数组,造成了一定的运行时间浪费。

四、欧拉筛法计算欧拉函数求和

4.1欧拉函数求和代码

欧拉筛法计算欧拉函数与欧拉筛法寻找素数算法一脉相承,主要是利用前文介绍的欧拉函数的性质,在寻找素数的同时计算欧拉函数,在掌握了欧拉筛法寻找素数算法后,会对本小节算法一目了然,代码如下:

unsigned euler_sum(unsigned upL)
{
	unsigned n = 0;
	unsigned phi[upL + 1] = {0}, prime[upL + 1] = {0}, isprime[upL + 1] = {0};
	phi[1] = 1;
	unsigned res = 0;
	for(unsigned i = 2 ; i <= upL; i++)
	{
		if(!isprime[i]) prime[++n] = i, phi[i] = i - 1;
		for(unsigned j = 1; j <= n && i * prime[j] <= upL; j++)
		{
			isprime[prime[j] * i] = 1;
			if(i % prime[j] == 0)
			{
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			else
			{
				phi[i * prime[j]] = phi[i] * (prime[j] - 1);
			}
		}
		//cout<<i<<','<<phi[upL]<<endl;
	}
	//cout<<phi[upL]<<endl;
	for(unsigned i = 1 ; i <= upL; i ++) res += phi[i];
	return res;
}

欧拉筛法求解欧拉函数求和,算法复杂度同样为O(N)

4.2代码改进

4.1节给出的代码中在栈空间上定义了phi, prime, isprime三个数组,当输入的数较大时,可能会由于栈内存溢出而无法正确运行,有两种方法进行改进,第一种是使用vector类型数组,第二种是改进代码,将prime, isprime两个数组合并为一个数组,下面分别进行介绍。

4.2.1使用vector类型数组

vector是C++标准库中的容器,在堆上分配内存,并且自动管理内存的分配和释放,确保不会发生内存泄漏,代码如下:

unsigned euler_sum(unsigned upL)
{
	unsigned n = 0;
	vector<unsigned> phi(upL + 1), prime(upL + 1), isprime(upL + 1);
	phi[1] = 1;
	unsigned res = 0;
	for(unsigned i = 2 ; i <= upL; i++)
	{
		if(!isprime[i]) prime[++n] = i, phi[i] = i - 1;
		for(unsigned j = 1; j <= n && i * prime[j] <= upL; j++)
		{
			isprime[prime[j] * i] = 1;
			if(i % prime[j] == 0)
			{
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			else
			{
				phi[i * prime[j]] = phi[i] * (prime[j] - 1);
			}
		}
		//cout<<i<<','<<phi[upL]<<endl;
	}
	//cout<<phi[upL]<<endl;
	for(unsigned i = 1 ; i <= upL; i ++) res += phi[i];
	return res;

上述代码只在三个数组定义处进行更改,其余地方未变。

4.2.2优化数组使用

由于1~N范围内的素数个数一定小于N,那么可以将prime, isprime两个数组合并,代码如下:

unsigned euler_sum(unsigned upL)
{
	unsigned n = 0;
	unsigned phi[upL + 1] = {0}, prime[upL + 1] = {0};
	phi[1] = 1;
	unsigned res = 0;
	for(unsigned i = 2 ; i <= upL; i++)
	{
		if(!prime[i]) prime[++n] = i, phi[i] = i - 1;
		for(unsigned j = 1; j <= n && i * prime[j] <= upL; j++)
		{
			prime[prime[j] * i] = 1;
			if(i % prime[j] == 0)
			{
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			else
			{
				phi[i * prime[j]] = phi[i] * (prime[j] - 1);
			}
		}
		//cout<<i<<','<<phi[upL]<<endl;
	}
	//cout<<phi[upL]<<endl;
	for(unsigned i = 1 ; i <= upL; i ++) res += phi[i];
	return res;
}

此算法可以显著降低内存占用,降低空间复杂度。

参考文档

初等数论(潘承洞 潘承彪)
C++程序设计竞赛真题实战特训教程
算法竞赛入门经典(第2版) (刘汝佳)

相关文章:

  • 【Gin-Web】Bluebell社区项目梳理6:限流策略-漏桶与令牌桶
  • 服务器能否拒绝非浏览器发起的HTTP请求?
  • css文本两端对齐
  • 微信小程序 - 条件渲染(wx:if、hidden)与列表渲染(wx:for)
  • LeetCode-34. 在排序数组中查找元素的第一个和最后一个位置
  • 本地开发用ASP.NET Core Web API项目创建及测试
  • AF3 中扩散模型类总结
  • 使用C++实现简单的TCP服务器和客户端
  • 山东大学软件学院nosql实验四
  • docker离线安装及部署各类中间件(x86系统架构)
  • 【Microsoft PowerPoint for Mac】2分钟配置-MAC一键删除PPT中的所有备注
  • MTK Android12 预装apk可卸载
  • 一文讲解Redis中和本地缓存之间的一致性问题及区别等
  • 用Deepseek直接在word中完成论文的润色(中-中,中-英, 英-中)
  • 如何安装VMware
  • 基于不完整多模态神经影像的阿尔茨海默病诊断领域特定信息保留|文献速递-医学影像人工智能进展
  • 深度学习训练平台建设中的性能优化实践
  • [特殊字符]清华大学:DeepSeek从入门到精通.pdf(清华领航,驾驭DeepSeek,开启AI新境界)
  • ZT11 压缩二维码
  • 【目标检测】目标检测中的数据增强终极指南:从原理到实战,用Python解锁模型性能提升密码(附YOLOv5实战代码)
  • 做旅游网站平台合作入驻/微信营销工具
  • 企业网站打包下载/网站访问量统计工具
  • 个人衣服定制店铺/seo做的比较好的公司
  • 成都个人网站制作公司/360优化大师下载官网
  • 做企业网站有什么工作内容/如何让自己的网站被百度收录
  • 政府网站建设思路汇报/百度app下载安装官方免费下载