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

《算法笔记》总结No.11——数字处理(上)欧拉筛选

机试中存在部分涉及到较复杂数字的问题,这是编码的基本功,各位一定要得心应手。 

目录

一.最大公约数和最小公倍数

1.最大公约数

2.最小公倍数

二.素数

1.判断指定数

2.输出所有素数

3.精进不休——埃拉托斯特尼筛法

4.达到更优!——欧拉筛法


 

一.最大公约数和最小公倍数

初学就开始的老生常谈,比较基础,大一期末考试和普通学校考研的难度,一定不要掉以轻心。

1.最大公约数

这里我们用欧几里得算法来实现——其实也就是初中学过的辗转相除法,没什么难度~

int gcb(int x,int y)
{if(x<y)  //保证前者更大一些 {int temp=x;x=y;y=temp;}while(y!=0){int temp=y;y=x%y;x=temp;} return x;
}

2.最小公倍数

设最大公约数是z,则x和y的最小公倍数就是x*y/z,这个公式也是小学知识,不要忘了;要是考试的时候真不小心忘了,就用暴力枚举吧,从x和y大的一个开始直到第一个可以同时整除x和y的元素即为最小公倍数~        

int main() {int x=0,y=0; cout<<"请输入两个数:";cin>>x>>y;cout<<"最大公约数是:"<<gcb(x,y)<<endl;cout<<"最小公倍数是:"<<(x*y)/gcb(x,y)<<endl;}

没什么问题:

二.素数

1.判断指定数

        常规的暴力枚举复杂度度为O(N),其实有更为简洁的办法——即对目标数开根号,比如对于16来说,2就是其的一个约数,但是16/2也是其一个约数,显然我们并不需要枚举到8——因为对于一个数来说,既然能整除,那么约数肯定是成对出现的,因此其中一个肯定比16开根号小,另一个则肯定大所以我们只需要枚举到4(也就是开根号),就能判断目标数字是否为素数~

bool IsPrime(int x)
{int temp=sqrt(x);for(int i=2;i<=temp;i++)if(x%i==0)return false;return true;
}

非常简单,不再赘述~ 

2.输出所有素数

上面函数已经有了,我们只需要枚举范围内的元素并调用函数,即可输出全部的素数:

int main() {int  x=0;cout<<"请输入查询的最大值:"; cin>>x;int count=0;for(int i=1;i<=x;i++){if(IsPrime(i)){cout<<i<<" ";count++;}if(count==5){cout<<endl;count=0;}}
}

没什么bug,count是为了输出更美观附加的: 

3.精进不休——埃拉托斯特尼筛法

        不妨这样思考一下:假设2是素数的话,那么他的倍数——4/6/8/10等等,一切可以整除2的数——是不是都不是素数!因此当我们找到一个素数时,如果将他的全部倍数都标记为合数,岂不是大大增加了效率。事实上,这就是埃拉托斯特尼筛法,其复杂度为LogN的平方,复杂度还要小于前面的N*根号N!代码如下:

#include <iostream>
#include <vector>
#include <cmath>using namespace std;void Eratos(int x)
{vector<int> Num,answer;Num.push_back(-1);//统一vector中的数字与下标 for(int i=1;i<=x;i++)Num.push_back(0); //初始化数组,如果下标i是0,则代表i是素数for(int i=2;i<x;i++){if(Num[i]==0){answer.push_back(i);for(int j=i+i;j<x;j+=i)//如果i是素数,则i所有的倍数都不是素数! Num[j]=1;	}	} for(int k=0;k<=answer.size()-1;k++)cout<<answer[k]<<" "; 
}int main() {int  x=0;cout<<"请输入查询的最大值:"; cin>>x;Eratos(x);
}

没什么问题:

 

诸位不妨仔细品味一下这个筛选的方法及其实现——是不是又有散列,又有二分的思想?何其妙哉~ 

4.达到更优!——欧拉筛法

        实际上,埃拉托斯特尼筛法还是有其优化的余地:比如6、10两个数字:按照其规则,这两个数在2的时候已经判断不是素数了,但是当枚举到3和5的时候,实际上还要再判断一次!

        因此不妨保证——每个合数只是被自己最小的质因数找到,这样就避免了重复的筛选步骤。为了防止大家晕,这里修改一下埃式筛的代码:

#include <iostream>
#include <vector>
#include <cmath>using namespace std;void Eratos(int x)
{int times=0;int count=0;//记录当前素数的个数vector<int> answer;//存放所有的素数vector<int> Num;//标记Num.push_back(0);//统一下标和数字大小 for(int i=1;i<=x;i++)Num.push_back(0);for(int i=2;i<=x;++i){if(!Num[i]){answer.push_back(i);count++;}for(int j=0;j<count;++j){if(i*answer[j]>x)break;int temp=i*answer[j];Num[temp]=1;times++;
//			if (i % answer[j] == 0)
//                break;}} for(int k=0;k<=answer.size()-1;k++)cout<<answer[k]<<" ";cout<<endl;cout<<"共标记了:"<<times<<"次!";
} int main() {int  x=0;cout<<"请输入查询的最大值:"; cin>>x;Eratos(x);
}

我们来测试一下100,可以发现标记合数的步骤一共执行了104次:

 

我们仔细回溯一下如上代码的运行流程:

  • 当i=2时,是素数,因此放到answer数组中;接下来遍历answer数组,2*2=4,因此4肯定不是素数,标记为合数
  • 接下来i=3,是素数,因此放到answer数组中;接下来遍历answer,3*2=6,3*3=9,因此6和9均被标记为合数
  • 接下来i=4,不是素数,直接遍历answer,2*4=8,3*4=12,8应该被标记为合数,但是对于12,其最小约数是2,因此应该由6*2来标记,所以此刻应该直接跳过

因此就有了有欧拉的写法:

void Eratos(int x)
{int times=0;int count=0;//记录当前素数的个数vector<int> answer;//存放所有的素数vector<int> Num;//标记Num.push_back(0);//统一下标和数字大小 for(int i=1;i<=x;i++)Num.push_back(0);for(int i=2;i<=x;++i){if(!Num[i]){answer.push_back(i);count++;}for(int j=0;j<count;++j){if(i*answer[j]>x)break;int temp=i*answer[j];Num[temp]=1;times++;if (i % answer[j] == 0)break;}} for(int k=0;k<=answer.size()-1;k++)cout<<answer[k]<<" ";cout<<endl;cout<<"共标记了:"<<times<<"次!";
}

核心在于这个:大家自行品味妙处——对于上面来说,因为4已经遇到了最小质因数2,因此应该直接跳出循环!

 运行100以内的素数,只执行了74次!

我们再来拿1000测试一下:

 

 

埃氏筛用了1400多次,而欧式筛只用了800多次,高低立判!


        今天就先总结到这,希望如上的素数搜索,对各位思考算法的意义有所启发——当人力无法计算庞大的运算量时,计算机应运而生;而计算机由于计算方式的不同,效率也不尽相同。我们追求高效简洁的算法,因为越低的耗时标志着越高的生产力——而相信各位都学过马克思主义基本原理:社会变革的根本原因是生产力的发展~博主有幸拜读过《人月神话》,相信大家都清楚【银弹】对于软件工程的意义。或许对于银弹的不懈追求,正是人类能够进化的原因~

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 数据结构与算法-随机快速排序
  • Linux:bash在被调用时会读取哪些启动文件?
  • SQL labs-SQL注入(三,sqlmap使用)
  • 实习心得list
  • Vue 3 实现左侧列表点击跳转滚动到右侧对应区域的功能
  • 高速板开源项目学习(二)
  • 【Node.js基础05】包的理解与使用
  • BUUCTF [MRCTF2020]Ezpop
  • LLM 大语言模型显存消耗估计与计算
  • C语言 —— 函数
  • 如何在Linux上安装配置RabbitMQ消息队列
  • json的数据结构
  • 测试用例的设计方法
  • AOP面向切面编程和log4j的使用(Java版)
  • 多列传感器数据多项式拟合python脚本
  • $translatePartialLoader加载失败及解决方式
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • ComponentOne 2017 V2版本正式发布
  • E-HPC支持多队列管理和自动伸缩
  • hadoop集群管理系统搭建规划说明
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • Java的Interrupt与线程中断
  • MaxCompute访问TableStore(OTS) 数据
  • Python学习之路13-记分
  • vue.js框架原理浅析
  • 机器学习学习笔记一
  • 那些被忽略的 JavaScript 数组方法细节
  • 适配mpvue平台的的微信小程序日历组件mpvue-calendar
  • 微服务框架lagom
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 一起参Ember.js讨论、问答社区。
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • ​学习笔记——动态路由——IS-IS中间系统到中间系统(报文/TLV)​
  • #1014 : Trie树
  • #if和#ifdef区别
  • ( 10 )MySQL中的外键
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (7)STL算法之交换赋值
  • (Java)【深基9.例1】选举学生会
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (理论篇)httpmoudle和httphandler一览
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (一) storm的集群安装与配置
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .net连接MySQL的方法
  • [android] 看博客学习hashCode()和equals()
  • [Asp.net MVC]Asp.net MVC5系列——Razor语法
  • [BUG] Authentication Error
  • [BUG]Datax写入数据到psql报不能序列化特殊字符
  • [C# 开发技巧]如何使不符合要求的元素等于离它最近的一个元素
  • [C/C++]数据结构 循环队列