原始方法(试除法)
- 最原始的素数判断方法
- 思路很简单,就是暴力从 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 时,可以直接跳出循环。
结束语
素数筛有些部分确实不是很容易让人理解,本蒟蒻也是理解了很久。
如果实在难以理解先背下来应用再慢慢理解也是个不错的选择。