【算法设计-枚举、分治】素数、约数、质因数分解
1. 素数判定
判定从 2 到sqrt(n)
依次能否把 n 整除,若存在可以整除的数则说明 n 不是素数,若都不可以整除则说明 n 是素数。
注意:2 是特殊的素数。
为什么到sqrt(n)
就可以了呢?请观察下面两个合数的例子:
30 分解为两个因数相乘:
- 2 x 15
- 3 x 10
- 5 x 6
- 6 x 5
- 10 x 3
- 15 x 2
36 分解为两个因数相乘:
- 2 x 18
- 3 x 12
- 4 x 9
- 6 x 6
- 9 x 4
- 12 x 3
- 18 x 2
发现当越过sqrt(n)
后,得到的两个因数与sqrt(n)
前相同(只是位置对调了而已),因此没有必要对sqrt(n)
后的数进行试除。
#include <cstdio>
#include <cmath>
using namespace std;
bool isPrime (int x){
if (x == 2)
return true;
else{
int bound = sqrt(x);
for (int i = 2; i <= bound; i++)
if (x % i == 0) return false;
}
return true;
}
int main(){
int n;
while (scanf("%d", &n) != EOF){
bool flag = isPrime(n);
if (flag)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
2. 素数筛选法
原理:
- 2 是素数,把 2 后面所有能被 2 整除的数都划去;
- 2 后面第一个没划去的数是 3,把 3 留下,3 后面所有能被 3 整除的数都划去;
- 3 后面第一个没划去的数是 5,把 5 留下,5 后面所有能被 5 整除的数都划去;
- 5 后面第一个没划去的数是 7,把 7 留下,7 后面所有能被 7 整除的数都划去;
- ...
注意:每次划去当前质数的倍数时,可能存在某些数被重复筛选的情况,如 8 既被 2 又被 4 筛选。在枚举筛选的时候可以进行剪枝,当 i 为素数时,注意到i * k (k < i)
必定已经在求得 k 的某个素数因子时被标记过,因此可以从i * i
开始。
#include <math.h>
#include <stdio.h>
#include <string.h>
#define MAX 10000
bool isPrime[MAX+1];
int main(){
int n;
scanf("%d", &n);
memset(isPrime, true, sizeof(isPrime)); // memset函数包含于string.h头文件中
for (int i = 2; i <= sqrt(n); i++){
if (isPrime[i]){ // 发现是素数,下面将素数的倍数都标记为非素数
for (int j = i * i; j < n; j += i) // i*k(k<i)必定已经在求得k的某个素数因子时被标记过,因此从i*i开始
isPrime[j] = false;
}
}
for (int i = 2; i < n; i++)
if (isPrime[i]) printf("%d ", i);
return 0;
}
3. 质因数分解
输入:
994
输出:
2 7 71
代码:
#include <math.h>
#include <stdio.h>
#include <string.h>
#define MAX 10000
bool isPrime[MAX+1];
int main(){
int n;
scanf("%d", &n);
memset(isPrime, true, sizeof(isPrime)); // memset函数包含于string.h头文件中
int bound = sqrt(n);
// 标记素数
for (int i = 2; i <= bound; i++){
if (isPrime[i]){ // 发现是素数,下面将素数的倍数都标记为非素数
for (int j = i * i; j < n; j += i) // i*k(k<i)必定已经在求得k的某个素数因子时被标记过,因此从i*i开始
isPrime[j] = false;
}
}
// 分解质因数
for (int i = 2; i <= bound; i++){
if (isPrime[i]){ // 如果是质数,则开始试除
while (n % i == 0){ // 若发现能整除,则继续使用这个质数除下去
n = n / i;
printf("%d ", i);
}
}
}
if (n > 1) // 若除完后,结果不是1,说明剩下来的是质数
printf("%d", n);
return 0;
}
4. 求一个数的约数
在自然数(0和正整数)的范围内,
4的正约数有:1、2、4。
6的正约数有:1、2、3、6。
10的正约数有:1、2、5、10。
12的正约数有:1、2、3、4、6、12。
15的正约数有:1、3、5、15。
18的正约数有:1、2、3、6、9、18。
20的正约数有:1、2、4、5、10、20。
注意:一个数的约数必然包括1及其本身。
#include <cstdio>
#include <cmath>
using namespace std;
int main(){
int n;
while (scanf("%d", &n) != EOF){
for (int i = 1; i <= sqrt(n); i++){
if (n % i == 0){
printf("%d %d ", i, n / i);
}
}
printf("\n");
}
return 0;
}
5. 求两个数的最大公约数(GCD)
辗转相除法:两个整数的最大公约数等于其中较小的数和两数相除的余数的最大公约数,即gcd(a,b) = gcd(b, a mod b)
。
基本思想:分治。
原理:若整数 g 为 a、b 的最大公约数,则有:
a = g x l
(1)
b = g x m
(2)
a、b 又可以表示为:
a = b x k + r
(即a / b = k···r
)(3)
把(1)(2)代入到(3):
g x l = g x m x k + r
,即r = g x (l - m x k)
注意到r = a mod b
,因此a mod b = g x (l - m x k)
(4)
联合(2)(4),这样问题变为了求 b 和 a mod b 的最大公约数:
b = g x m
(2)
a mod b = g x (l - m x k)
(5)
递归写法:
// 辗转相除法求最大公约数(12和18的最大公约数:6)
int gcd (int a, int b){
if (b == 0)
return a;
else
return gcd(b, a % b);
}
非递归写法:
// 辗转相除法求最大公约数(12和18的最大公约数:6)
int gcd (int a, int b){
while (b != 0){
int rem = a % b;
a = b;
b = rem;
}
return a;
}
6. 求两个数的最小公倍数(LCM)
// 求最小公倍数(12和18的最小公倍数:36)
int lcm (int a, int b){
return a * b / gcd(a, b);
}