昨晚,群里面有个人问了个判断素数的题目
题目如下:
链接:筛选法求素数__牛客网
来源:牛客网
用筛选法求n以内的素数。筛选法求解过程为:将2~n之间的正整数放在数组内存储,将数组中2之后的所有能被2整除的数清0,再将3之后的所有能被3整除的数清0 ,以此类推,直到n为止。数组中不为0 的数即为素数。
一开始没看明白题目意思,以为只需要满足不是2或者3的倍数就可以了,然后发现出错了,后来才发现搞错掉了
然后我就查了下判断素数的方法,有如下几个,但不止这几个:1.AKS算法——公认最优的算法(但实际应用中不常见) 2.Miller—Rabbin算法(基于费马小定理的扩展) 3.试除法(优化6倍法) 4.筛选法
1.AKS算法
直说吧,没看懂,太复杂,我也不会,但从GPT那里获得了c++的简易模板
代码如下:
#include <iostream>
#include <cmath>
using namespace std;
typedef long long ll;
// 求最大公约数的辗转相除法
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}
// 快速幂算法
ll binpow(ll a, ll b, ll m) {
ll res = 1;
a %= m;
while (b > 0) {
if (b & 1) {
res = res * a % m;
}
a = a * a % m;
b >>= 1;
}
return res;
}
// 判断一个数是否为素数
bool is_prime(ll n) {
if (n <= 1) {
return false;
}
if (n <= 3) {
return true;
}
if (n % 2 == 0 || n % 3 == 0) {
return false;
}
ll r = sqrt(n);
for (ll i = 5; i <= r; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) {
return false;
}
}
return true;
}
// 判断n是否是a的幂
bool is_power(ll n) {
if (n <= 1) {
return true;
}
ll r = sqrt(n);
for (ll a = 2; a <= r; ++a) {
ll p = a;
while (p <= n) {
p *= a;
if (p == n) {
return true;
}
}
}
return false;
}
// AKS算法判断n是否为素数
bool AKS(ll n) {
// 如果n是a的幂,则n不是素数
if (is_power(n)) {
return false;
}
// 寻找r,满足n^r-1 = (n-1)q,其中q为素数
ll r = 2;
while (r <= sqrt(n)) {
if (n % r == 0) {
return false;
}
ll phi_r = r - 1;
if (gcd(n, r) == 1) {
bool is_valid = true;
for (ll a = 2; a <= sqrt(phi_r) && is_valid; ++a) {
if (gcd(a, r) == 1) {
is_valid = binpow(a, phi_r, n) != 1;
}
}
if (is_valid) {
ll q = (n - 1) / r;
if (gcd(q, r) == 1 && binpow(n, q, r) == 1) {
return true;
}
}
}
++r;
}
return false;
}
int main() {
ll n;
cin >> n;
if (AKS(n)) {
cout << n << " is prime" << endl;
} else {
cout << n << " is not prime" << endl;
}
return 0;
}
代码不一定正确,经供参考,它太复杂了,所以我直接放弃了,而且实际应用中也不常见
2.Miller—Rabbin算法
话不多说,直接上代码
typedef long long ll;
// 判断一个数是否为素数,返回true表示是素数,返回false表示不是素数
bool is_prime(ll n) {
// 1. 如果要判断的数n等于2或3,则判定为素数
if (n == 2 || n == 3) return true;
// 2. 如果n可以表示为a^b的形式,则判定n为合数
for (ll a = 2; a * a <= n; ++a) {
ll b = n;
while (b > 1) {
ll m = a, x = 1;
while (b > 0) {
if (b & 1) x = x * m % n;
m = m * m % n;
b >>= 1;
}
if (x == n) return false;
if (x > 1 && x * x % n == 1) break;
}
}
// 3. 如果n不是奇数,则判定n为合数
if (n % 2 == 0) return false;
// 4. 计算r使得r是最小的满足下列不等式之一的整数
ll r = 1, d = (n - 1) / 2;
while (d % 2 == 0) {
r++;
d /= 2;
}
// 如果不存在满足上述条件的r,则n为素数
if (r == 1) return true;
// 5. 对所有满足1 < a ≤ r的整数a,判断是否满足条件之一
for (ll a = 2; a <= r; ++a) {
// (i) a^n ≡ a mod n
ll b = n - 1, x = a % n;
while (b > 0) {
if (b & 1) x = x * a % n;
a = a * a % n;
b >>= 1;
}
if (x != a % n) return false;
// (ii) 对于所有0 < k < n,有(a^k) mod (x^r - 1, n) = 1
if (r < n - 1) {
ll y = a;
for (ll i = 0; i < r; ++i) {
if (y == 1) break;
y = y * y % n;
}
if (y != 1) return false;
}
}
// 6. 如果满足上述条件,则判定n为素数,否则n为合数
return true;
}
原理:
-
如果要判断的数n等于2或3,则判定为素数。
-
如果n可以表示为a^b的形式,则判定n为合数。
-
如果n不是奇数,则判定n为合数。
-
计算r使得r是最小的满足下列不等式之一的整数。
-
对所有满足1 < a ≤ r的整数a,判断是否满足条件之一:
(i) a^n ≡ a mod n (ii) 对于所有0 < k < n,有(a^k) mod (x^r - 1, n) = 1
-
如果满足上述条件,则判定n为素数,否则n为合数。
代码通过循环枚举所有满足条件的a和r,判断n是否为素数。其中,在计算r的过程中,使用了一种称为"二次探测"的方法,用来减少计算量和时间复杂度。
时间复杂度为:O(k * log^3 n),k取任意值,通常为20左右可满足需求
3.试除法(优化6倍法)
还是直接上代码
bool is_prime(int n) {
if (n <= 1) return false; // 1 不是素数
if (n <= 3) return true; // 2 和 3 是素数
if (n % 2 == 0 || n % 3 == 0) return false; // 排除掉 2 和 3 的倍数
int max_divisor = sqrt(n); //求待判断数开方数
for (int i = 5; i <= max_divisor; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) {
return false;
}
}
return true;
}
试除法(也称质因数分解法)是一种用来判断一个数是否为素数的算法。它的基本原理是将要判断的数 n 依次除以 2 到 sqrt{n} 之间的每个整数,如果都不能整除,则 n 是素数。如果存在能整除 n 的整数,则 n 不是素数。
在优化的试除法中,我们可以先判断 n 是否为 2 或 3,然后只对 6k±1 的形式进行试除,这样能够减少试除的次数,提高算法效率。具体实现中,我们只需要判断 6k±1 ≤ √n 是否成立即可。
需要注意的是,如果 n 为合数,它必定存在小于 sqrt{n} 的质因子,因此只需要试除 2 到 sqrt{n} 之间的数即可,不需要试除所有的数
4.筛选法
这也是本题要求的算法,代码如下:
#include <iostream>
#include <vector>
using namespace std;
vector<int> get_primes(int n) {
vector<int> primes;
vector<bool> is_prime(n + 1, true); // 初始化所有数为素数
is_prime[0] = is_prime[1] = false; // 排除0和1不是素数的情况
// 从2开始筛选,将所有的倍数标记为非素数
for (int i = 2; i * i <= n; i++) {
if (is_prime[i]) {
for (int j = i * i; j <= n; j += i) {
is_prime[j] = false;
}
}
}
// 收集所有的素数
for (int i = 2; i <= n; i++) {
if (is_prime[i]) {
primes.push_back(i);
}
}
return primes;
}
int main() {
int n;
cin >> n;
vector<int> primes = get_primes(n);
for (int prime : primes) {
cout << prime << " ";
}
cout << endl;
return 0;
}
它的基本思想是先假设所有数都是素数,然后从2开始,将其倍数标记为合数,最终剩下的未被标记的数即为素数。
ok,就到这儿吧,可能存在些许错误,若有发现,请指出,谢谢