埃拉托色尼筛选法巧解质数问题(埃氏筛法求解素数问题)

背景:

素数(即质数)问题是老问题了,程序员面试问题,大小比赛的热门题。

比如 最简单的判断素数问题,或者计算素数的个数,等等。

简单的判断素数函数:

素数的定义:指在大于1的整数中,只能被1和它本身整除的数。

1既不是素数也不是合数

2是最小的素数

试除法原理:非素数k必定可以被一个小于等于√k的素数整除。

int prime(int n)
{
	if(n==1)
	return 0;
	if(n==2)
	return 1;
    //函数调用 还有 循环的时间 对付一些入门简单题的
    //时间复杂度为√n
	for(ll i=2;i*i<=n;i++)
	{
		if(n%i==0)
            //如果有 能被n除的数 ,即n的因子不只是1和本身,不符合素数的概念
            //返回0
			return 0;
	}
	return 1;
}

埃氏筛法求素数:

题目:从2到N,每个数判断是否有1和它本身以外的其他因数。

首先将0、1排除:

对于初始队列{2,3,4,5,6,7,8,9,10,11,12,13,14……n},操作步骤如下:

(1)输出最小的素数2,然后筛选掉2的倍数,剩下{3,5,7,9,11,13,……}

(2)输出最小的素数3,然后筛选掉3的倍数,剩下{5,7,11,13,……}

(3)输出最小的素数5,然后筛选掉5的倍数,剩下{7,11,13,……}

继续上面步骤,直到队列为空。

初始化 p = 2,因为2是最小的质数;

枚举所有p的倍数(2p,3p,4p,…),标记为非质数(合数) 

找到下一个 没有标记 且 大于p 的数。如果没有,结束运算;如果有,将该值赋予p,重复步骤;

运算结束后,剩下所有未标记的数都是找到的质数。

 上动图理解一下:

 下面是程序,visit[i]记录i的状态,如果visit[i]=true,表示它被筛选掉了(不是素数)。用prime[]存放素数,例如prime[0]是第一个素数2。

const int maxn=1e7;//定义空间大小,约为10MB 
int prime[maxn+1];//存放素数,记录visit[i]=false的项 
bool visit[maxn+1];//true表示被筛掉(非素数) 
int E_sieve(int n)//埃氏筛法,计算[2,n]内的素数 
{
	int k=0;//统计素数的个数 
	for(int i=0;i<=n;i++)
		visit[i]=false;//初始化
	for(int i=2;i<=n;i++)
	{
		if(!visit[i])
		{
			prime[k++]=i;//i是素数,存入prime[]中 
			for(int j=2*i;j<=n;j+=i)//i的倍数都不是素数 
			{
				visit[j]=true;//标记为true筛掉 
			}
		} 
	} 
	return k; 
}

这个算法的核心思想是:任何一个p值都是质数,如果存在一个合数,那么就应该存在一个质数能够标记它。

算法的时间复杂度为:O(n/2+n/3+n/5+……)=O(n log log2 n)。

算法的空间复杂度为:当maxn=1e7时约为10MB。一般题目会限制空间为65MB,所以n不能再大了。

当然代码还有两处可以优化。

(1)用来做筛选的数为2,3,5……,最多到√n就可以了。

        例如求n=100以内的素数,用2,3,5,7筛选足够了。原理和试除法类似。

(2)for(int j=i*2;j<=n;j+=i)中的j=i*2可以优化为j=i*i。例如i=5时,2*5、3*5、4*5已经在前面i=2,3,4筛选过了。

优化后的代码为:

const int maxn=1e7;//定义空间大小,约为10MB 
int prime[maxn+1];//存放素数,记录visit[i]=false的项 
bool visit[maxn+1];//true表示被筛掉(非素数) 
int E_sieve(int n)//埃氏筛法,计算[2,n]内的素数 
{
	for(int i=0;i<=n;i++)
		visit[i]=false;//初始化
	for(int i=2;i*i<=n;i++)//筛选掉非素数 
	{
		if(!visit[i])
		{
			for(int j=i*i;j<=n;j+=i)//i的倍数都不是素数 
			{
				visit[j]=true;//标记为true筛掉 
			}
		} 
	} 
	int k=0;//统计素数的个数 
	for(int i=2;i<=n;i++)
	{
		if(!visit[i])
		{
			prime[k++]=i;//i是素数,存入prime[]中 
		} 
	} 
	return k; 
}

看到这里写一题练练手吧:http://onlinejudege.me/problem/1195​​​​​​

 可能会卡的进不去,学校的OJ不太行。题面在这里:

 超时我已经麻了。。。。

AC代码:

#include<iostream>
using namespace std;
typedef long long ll;
const int maxn=5000000;//定义空间大小,约为10MB
int mn[maxn+1];//存放素数,记录visit[i]=false的项
bool visit[maxn+1];//true表示被筛掉(非素数)
int flag=0;
int fun()
{
	int k=0;//统计素数的个数
	for(int i=0;i<maxn;i++)
		visit[i]=false;//初始化
	visit[0]=true;        
	visit[1]=true;
	for(int i=2;i*i<=maxn;i++)//筛选掉非素数
	{
		if(!visit[i])
		{
			for(int j=i*i;j<=maxn;j+=i)//i的倍数都不是素数
			{
				visit[j]=true;//标记为true筛掉
			}
		}
	}
	for(int i=0;i<maxn;i++)
        {
            if(!visit[i])
                mn[flag++]=i;
        }
    return flag;
}

int fun1(int *arr , int low , int high , int target)//递归实现二分
{
	int middle = (low + high)/2;
	if(low > high)
		return -1;
	if(arr[middle] <=target&&arr[middle+1] >target)
		return middle;
	if(arr[middle] > target)
		return fun1( arr , low , middle - 1 , target);
	if(arr[middle] < target)
		return fun1( arr , middle + 1 , high , target);
}

int main()
{
    std::ios::sync_with_stdio(false);
    int n,a,m,flag,flag1,flag2;
    flag1=fun();
	scanf("%d",&n);
	while(n--)
	{
        scanf("%d%d",&a,&m);
	    flag2=fun1(mn,0,flag1,a);
        printf("%d\n",mn[flag2+m]);
	}
	return 0;
}

拿去吧,拿去给皇上看,你跟他说,这都是本宫自己抄的,没有假手于人,本宫的手都要断了。

                                                                                                           ------------哈哈哈哈哈哈

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值