如何判断一个数是否为素数
暴力方法
判断一个数n是否为素数,只需在(1,n)看是否存在它的因子
bool IsPrime(int n)
{
if(n < 2)
return false;
else if(n == 2)
return true;
else{
for(int i = 2;i < n;++i)
if(n % i == 0)
return false;
return true;
}
}
优化版1
定理:如果n是合数,在\((1,\sqrt{n}]\)必定存在它的因子
证明:设\(n=ab\)
假设a,b都小于\(\sqrt{n}\),那么\(ab<\sqrt{n}\sqrt{n}=n\),矛盾
假设a,b都大于\(\sqrt{n}\),那么\(ab>\sqrt{n}\sqrt{n}=n\),矛盾
因此我们可以得知,只需在\((1,\sqrt{n}]\)中看是否存在它的因子即可,再者可先判断n是否为偶数(2除外),不是偶数的话,只需在\((1,\sqrt{n}]\)中的奇数看是否存在它的因子即可
bool IsPrime(int n)
{
if(n < 2)
return false;
else if(n == 2)
return true;
else if(!(n&1))//n是偶数
return false;
else
{
const int num = sqrt(n);
for(int i = 3;i <= num;i += 2)
if(n % i == 0)
return false;
return true;
}
}
优化版2
定理:任何一个合数=若干个素数的积
证明:
合数的因子如果有素数,那么定理成立,如果因子是合数,合数又可以分解,不断分解,最终分解出素数,由于素数是分解出的,所以肯定可以整除最初的合数
这样的话,我们只需在\((1,\sqrt{n})\)中的素数看是否存在它的因子即可,表明如果知道\((1,\sqrt{n}]\)所有质数,判断n是否为质数将变得很快
假设Primes[i]是递增的素数序列: 2, 3, 5, 7, ...,更准确地说primes[i]序列包含\((1,\sqrt{n}]\)范围内的所有素数
bool IsPrime(int primes[], int n)
{
if(n < 2)
return false;
const int limit = sqrt(n);
for(int i = 0;primes[i] <= limit;++i)
if(n%primes[i] == 0)
return false;
return true;
}
这样的话,我们只需找出\((1,\sqrt{n}]\)所有素数
构造素数列
原始方法
我们在构造的时候完全可以利用已经被构造的素数序列,假设我们已经得到素数序列: \(p_1, p_2, .. p_n\)
现在要判断\(p_{n+1}\)是否是素数, 则需要\((1, \sqrt{p_{n+1}}]\)范围内的所有素数序列,\(p_{n+1}\)为素数,\(p_{n+1} \% p_i == 0,(pi < \sqrt{p_{n+1}})\)
构造num个素数
void MakePrimes(int Primes[],const int num)//Primes指向的内存足够大
{
if(num == 1)
{
Primes[0] = 2;
return;
}
else if(num == 2)
{
Primes[0] = 2;
Primes[1] = 3;
return;
}
//下面作为已知的素数列
Primes[0] = 2;
Primes[1] = 3;
int sub = 2;
for(int current = 5;sub < num;++current)
{
bool flag = true;
const int max = sqrt(current);
for(int i = 0;Primes[i] <= max;++i)
if(current % Primes[i] == 0)
{
flag = false;
break;
}
if(flag)
Primes[sub++] = current;
}
}
不超过max的所有质数
int MakePrimes(int Primes[],const int max)//Primes指向的内存足够大
{
if(max < 2)
return 0;
else if(max == 2)
{
Primes[0] = 2;
return 1;
}
else if(max < 5)
{
Primes[0] = 2;
Primes[1] = 3;
return 2;
}
//下面作为已知的素数列
Primes[0] = 2;
Primes[1] = 3;
int sub = 2;
for(int current = 5;current < max;++current)
{
bool flag = true;
const int max = sqrt(current);
for(int i = 0;Primes[i] <= max;++i)
if(current % Primes[i] == 0)
{
flag = false;
break;
}
if(flag)
Primes[sub++] = current;
}
return sub;
}
sieve of Eratosthenes(埃拉托斯特尼筛法)
简介
给出要筛数值的范围max,找出max以内的素数\(p_1,p_2,p_3,..,p_k\)。
具体做法是:
- 列出2以后的所有序列:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ... - 标出序列中的第一个质数,也就是2,序列变成:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ... - 将剩下序列中,划摽2的倍数,序列变成:
3 5 7 9 11 13 15 17 19 21 23 25 ... - 如果现在最大的素数\(>\sqrt{max}\),那么剩下的序列中所有的数都是质数,否则用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉。再判断,再循环...
原理:保证每次得到的素数不会是前面数的倍数,也就是它的因子只有1和它本身
步骤解疑
- 为什么要判断最大素数>\(\sqrt{max}\)
因为如果数字i > \(\sqrt{max}\)且i是合数,则在\([2,\sqrt{i}]\)必有它的因子,它的因子会将i标记,而i的最大值是max,所以判断最大素数>\(\sqrt{max}\),这样会加快筛选 - 为什么不将合数的倍数标记
因为合数的倍数也是它的因子的倍数,它的因子肯定有素数,在前面已经将倍数标记了
代码
int MakePrimes(int Primes[],const int max)
{
if(max < 2)
return 0;
bool *list = new bool[max + 1];//bool[i]表示数字i是否为素数
memset(list,true,sizeof(bool)*(max + 1));
list[0] = list[1] = false;
list[2] = true;
const int limits = sqrt(max);
for(int i = 2;i <= limits;i++)
if(list[i])
for(int j = i + i;j <= max;j += i)
list[j] = false;
int sub = 0;
for(int i = 2;i <= max;i++)
if(list[i])
Primes[sub++] = i;
delete [] list;
return sub;
}
Eratosthenes改进
将上面从最大素数后面开始划掉素数的倍数
改成从最大素数的平方后面开始划掉素数的倍数
,也就是int j = i + i
改成int j = i×i
假设i是目前已知的最大素数,那么[2,i * i]之间的合数的因子必定在[2,i]之间,故已经被i前面的质数标记了,所以无需划掉
int MakePrimes(int Primes[],const int max)
{
if(max < 2)
return 0;
bool *list = new bool[max + 1];//bool[i]表示数字i是否为素数
memset(list,true,sizeof(bool)*(max + 1));
list[0] = list[1] = false;
list[2] = true;
const int limits = sqrt(max);
for(int i = 2;i <= limits;i++)
if(list[i])
for(int j = i * i;j <= max;j += i)
list[j] = false;
int sub = 0;
for(int i = 2;i <= max;i++)
if(list[i])
Primes[sub++] = i;
delete [] list;
return sub;
}
Sieve of Euler(欧拉筛法)
简介
欧拉筛法(Sieve of Euler)是埃拉托斯特尼筛法的一种改进。有人称其为快速线性筛法。回顾经典的埃拉托斯特尼筛法,它可能对同一个素数筛去多次,故时间复杂度为O(n log log n)。如果用某种方法使得每个合数只被筛去一次就变成是线性的了。不妨规定每个合数只用其最小的一个质因数去筛,这便是欧拉筛了,时间复杂度 O(n).
原理:每一个合数都可以分解成若干个素数相乘,在这些素数中,必有最小的一个。如果我们要划掉\((1,n]\)里面所有合数,如果我们讲变量i从2变化到n,对每个i,我们都将i × i前面已知所有素数划掉,那么这些素数恰好是被划掉的合数的最小素数因子,又由于i是从2变化到n,所以能保证每个合数都能被划掉,
推广
如果把int范围的素数存起来,下次只需要使用二分法查找该数是否在列表里即可
//将一个合数N分解为若干个质数的积,Hint:N的大于sqrt(N)的质因子至多有一个(sqrt(n)指N的开方取整)
void divide(int n)
{
const int limit = sqrt(n);
int p = new int[limit];
int num = MakePrimes2_(p,limit);
for(int i = 0;i < num && n > 1;)
{
if(n % p[i] == 0)
{
std::cout << p[i];
n /= p[i];
if(n != 1)
std::cout << "";
}
else
i++;
}
if(n > 1)//因为最多一个大于sqrt(n)的质数,就是这个
std::cout << n;
delete[] p;
}
int main()
{
/* const int max = 100;
int a[max];
int n = MakePrimes2_(a,max);
for(int i = 0;i < n;++i)
std::cout << a[i] << "\t";
std::cout << std::endl;
n = MakePrimes1_(a,max);
for(int i = 0;i < n;++i)
std::cout << a[i] << "\t";
std::cout << std::endl; */
return 0;
}