C/C++ 素数筛 ACM算法

原始方法(试除法)


  • 最原始的素数判断方法
  • 思路很简单,就是暴力从 2 一直除到 n-1
  • 能被整除就是合数,否则就是素数

Code:

int isPrime(int n)
{
	if(n==1)return 0;
	for(int i=2;i<n;i++)
	{
		if(n%i==0)return 0;
	}
	return 1;
}

问题:

  • 十分暴力,耗时过长,TLE 致死

原始方法改进版


  • 如果一个数是非素数,那么它的因子一定成对出现
    例如:8 = 2 x 4 ,15 = 3 x 5 ,16 = 4 x 4
  • 基于以上原理,只需判断一个数 小的因子是否存在 即可
  • 于是判断到根号 n 即可

Code:

int isPrime(int n)
{
	if(n==1)return 0;
	for(int i=2;i<=sqrt(n);i++)
	{
		if(n%i==0)return 0;
	}
	return 1;
}

问题:

  • 对于很大的数据这样计算依然会消耗大量的时间
  • 在多组数据中可能会判断同一个数多次

因此我们需要通过素数筛来加快素数的判断。

埃氏筛-埃拉托斯特尼(Eratosthenes)筛法


原理:

  • 首先默认 2 到 n 所有整数都为素数(当然是不可能的了)
  • 从最小的素数 2 开始,将所有的 2 的倍数标记为非素数
  • 此时剩下的最小的素数就是3,再将所有 3 的倍数标记为非素数
  • 如果剩下最小的数是 m,那么 m 就是素数。将所有 m 的倍数标记为非素数
  • 这样不断进行下去,就能依次获得 n 以内的所有素数

说明:

  • 为了对 C 语言的友好一点,以下代码中的数组不使用 bool 使用 char
  • prime[i] 为 1 代表是素数 0 代表不是素数
  • 素数筛法是一种以空间换取时间的算法

Code:

#define MAXN 1008611
char prime[MAXN];
void getPrime()
{
	memset(prime,1,sizeof(prime)); //默认全为素数
	prime[0]=prime[1]=0; //0和1不是素数
    for(int i=2;i<MAXN;i++)
    {
        if(prime[i]==1) //如果i是素数
        {
            for(int j=2;j*i<MAXN;j++)
			{
				prime[i*j]=0; //将所有i的倍数标记为非素数
			}
        }
    }
}

基于原始暴力法的改进,我们可以用同样的方法对埃氏筛进行改进。

#define MAXN 1008611
char prime[MAXN];
void getPrime()
{
	memset(prime,1,sizeof(prime));
	prime[0]=prime[1]=0;
    for(int i=2;i<=sqrt(MAXN);i++) //这里只需要判断到根号maxn
    {
        if(prime[i]==1)
        {
            for(int j=i*i;j<MAXN;j+=i) //这里可以从i*i开始判断,因为之前的合数已经被更小的素数筛掉了
			{
				prime[j]=0;
			}
        }
    }
}

FAQ:

  • Q:为什么 可以用 char 数组,不可以用 int 数组 呢?
  • 因为 memset 赋值时按照字节赋值
  • char 类型占 1 字节,因此可以用 memset 将数组全部赋值为 1 (0x01)
  • int 类型占 4 字节,如果用 memset 进行赋值,会得到 0x01010101

偷偷问一下屏幕前的你:你知道FAQ英文全称是啥吗?
科普一下 FAQ (Frequently Asked Questions) 常见问题

问题:

  • 一个合数可能被多个素数筛选,无疑会浪费一部分时间。
  • 例如 70 = 2 x 35 = 5 x 14 = 7 x 10

欧拉筛-欧拉(Euler)筛法


原理:

  • 以埃氏筛为基础,使每个非素数只被它的最小质因子筛一次

Code:

#define MAXN 1008611
char prime[MAXN]; //是否为素数
int primeList[MAXN],num=0; //素数表和素数个数
void getPrime()
{
	memset(prime,1,sizeof(prime));
	prime[0]=prime[1]=0;
    for(int i=2;i<MAXN;i++)
    {
    	if(prime[i])primeList[num++]=i; //是素数则加入素数表
    	for (int j=0;j<num&&i*primeList[j]<MAXN;j++)
		{
            prime[i*primeList[j]]=0; //筛掉所有质数的i倍
            if (i%primeList[j]==0)break; //避免重复筛
        }
    }
}

关于 if (i%primeList[j]==0)break; 的解释:

  • 我们不难发现这句话很难理解 (╯‵□′)╯︵┻━┻+
  • 我们可以利用数学方法进行推导 (ノ*・ω・)ノ
  • 当 i 是 prime[j] 的整数倍时,设 i = k * primeList[j]
  • ∵ i * primeList[j+1] = (k * primeList[j+1]) * primeList[j]
  • ∴ i * primeList[j+1] 是 primeList[j] 的整数倍
  • 所以 i 之后的数 * primeList[j] 会被 primeList[j+1] * 另一个 i 筛掉
  • 因此当 i%primeList[j]==0 时,可以直接跳出循环。

结束语


素数筛有些部分确实不是很容易让人理解,本蒟蒻也是理解了很久。
如果实在难以理解先背下来应用再慢慢理解也是个不错的选择。
在这里插入图片描述

发布了32 篇原创文章 · 获赞 104 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/csg999/article/details/103916506
今日推荐