一、判断质数
判断质数的思想实际上非常朴素,我们知到,当一个数不能被除了1和它本身之外的数整除的时候,这个数就是质数,当然,质数是从2开始算的,<= 1的数没有质数的概念
算法思想:我们从2~ n - 1 开始枚举,只要能找到一个数可以整除n,那么n就不是质数
给定 nn 个正整数 aiai,判定每个数是否是质数。
输入格式
第一行包含整数 nn。
接下来 nn 行,每行包含一个正整数 aiai。
输出格式
共 nn 行,其中第 ii 行输出第 ii 个正整数 aiai 是否为质数,是则输出
Yes
,否则输出No
。数据范围
1≤n≤1001≤n≤100,
1≤ai≤231−11≤ai≤231−1输入样例:
2 2 6
输出样例:
Yes No
import java.util.*;
public class Main{
public static boolean is_prime(int x) {
if (x < 2) return false; // < 2 的数它一定不是质数
for (int i = 2; i <= x; i ++) {
if (x % i == 0) return false; //可以被 1 和 它本身的之外的数整除的数一定不是质数
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n -- > 0) {
int x = sc.nextInt();
if (is_prime(x)) System.out.println("Yes");
else System.out.println("No");
}
}
}
时间复杂度?
我们通过观察代码可以发现,我们循环的次数固定是 2 ~ n - 1 ,因此时间复杂度稳稳的 O(N)
能否优化?
我们通过数学分析,如果一个数n可以被x整除,那么它一定也可以被 n / x 整除,那么我们循环的时候i的取值只需要最大达到根号n(i*i <= n),我们只需要计算 2 ~ 根号n,但是每次循环的话计算根号n比较费时间,我们可以采用 i <= n / i 的写法(不要采用 i *i <= n ,因为存在int溢出的情况)
优化完之后时间复杂度来到根号n
import java.util.*;
public class Main{
public static boolean is_prime(int x) {
if (x < 2) return false; // < 2 的数它一定不是质数
for (int i = 2; i <= x / i; i ++) { //只是换了循环退出条件,时间复杂度由 N 到 根号 N
if (x % i == 0) return false; //可以被 1 和 它本身的之外的数整除的数一定不是质数
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n -- > 0) {
int x = sc.nextInt();
if (is_prime(x)) System.out.println("Yes");
else System.out.println("No");
}
}
}
二、分解质因数
在我们小学的时候我们学过,任何一个合数都可以被拆分为质数相乘的形式
同样的,一个数n如果是由多个质因数相乘得到的,那么它的质因数中最多只有一个可以大于根号n,(再多一个乘积就大于n了),因此我们依旧采用试除法可以很轻松得到分解质因数的代码
给定 nn 个正整数 aiai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。
输入格式
第一行包含整数 nn。
接下来 nn 行,每行包含一个正整数 aiai。
输出格式
对于每个正整数 aiai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。
每个正整数的质因数全部输出完毕后,输出一个空行。
数据范围
1≤n≤1001≤n≤100,
2≤ai≤2×1092≤ai≤2×109输入样例:
2 6 8
输出样例:
2 1 3 1 2 3
import java.util.*;
public class Main{
public static void divide(int n) {
for (int i = 2; i <= n / i; i ++) {
if (n % i == 0) {
int res = 0; // 计算指数
while (n % i == 0) { //只要这个数还可以被这个质数除 那就狠狠地除!
n = n / i; //如果x可以被除尽,那么n最后一定是1
res++;
}
System.out.println(i + " " + res);
}
}
if (n > 1) System.out.println(n + " " + 1);
System.out.println();
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n -- > 0) {
int x = sc.nextInt();
divide(x);
}
}
}
三、筛质数
给定一个正整数 nn,请你求出 1∼n1∼n 中质数的个数。
输入格式
共一行,包含整数 nn。
输出格式
共一行,包含一个整数,表示 1∼n1∼n 中质数的个数。
数据范围
1≤n≤1061≤n≤106
输入样例:
8
输出样例:
4
1、朴素筛法
首先说一下朴素做法地思想吧:
我们用一个st数组来标记素数,如果当前我们遍历地点没有被标记为合数,那它肯定是质数(下边会解释),我们把它添加到质数数组中即可,其次不管这个数是不是质数,我们都用它去标记他到n之间倍数为合数,好了,为什么遍历到当前的数没被标记过一定是质数呢?我们知道任何一个合数是一定可以被分为几个质数相乘地形式的,按我们之前地倍数更新法,合数一定会被标记,质数之所以没有被标记是因为它地因数只有自己和1,没有因数通过倍数来更新它!
import java.util.*;
public class Main{
static int cnt = 0;
static int[] primes = new int[1000010];
static boolean[] st = new boolean[1000010];
public static void find(int n) {
for (int i = 2; i <= n; i ++) {
if (!st[i]) { //i 是质数
primes[cnt++] = i; //存储质数
}
for (int j = i + i; j <= n; j += i) { //更新合数
st[j] = true;
}
}
System.out.println(cnt);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
find(n);
}
}
2、埃氏筛
埃氏筛其实是对朴素做法地一种改进,我们只需要把质数的倍数标记为合数(合数的倍数一定是被质数更新过了,因为合数一定可以分解为质数相乘),这样优化之后的时间复杂度是O(nloglogn)
import java.util.*;
public class Main{
static int N = 1000010;
static int cnt = 0;
static int[] primes = new int[N];
static boolean[] st = new boolean[N];
public static void find(int n) {
for (int i = 2; i <= n; i ++) {
if (!st[i]) { // i 一定是质数
primes[cnt++] = i;
for (int j = i + i; j <= n; j += i) { //只是把更新操作放在了质数的情况里面
st[j] = true;
}
}
}
System.out.print(cnt);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
find(n);
}
}
3、线性筛
对于埃氏筛法的进一步改进,我们控制合数只会被它的最小质因子标记,时间复杂度O(n)
import java.util.*;
public class Main{
static int N = 1000010;
static int cnt;
static int[] primes = new int[N];
static boolean[] st = new boolean[N];
public static void find(int n) {
for (int i = 2; i <= n; i ++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0;primes[j] <= n / i; j ++) { //思考这一步如何保证合数只会被第一个质因子更新呢?
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
System.out.print(cnt);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
find(n);
}
}
解释一下更新合数的代码:
(简称prime【j】为 pj
1、当我们 i % pj == 0 时,我们可以肯定pj * i 的最小质因子一定是 pj (因为我们是用最小的质因子从小到大更新的),我们之后break掉,可以肯定这个合数只被它的最小质因子更新
2、如果 i % pj != 0,由于我们pj从小到大,所以i的最小质因子一定是大于 pj的,我们用pj更新标记pj * i 理所应当
3、为什么合数一定会被标记?
本质上还是因为合数一定可以被拆分为质数相乘的形式,我们一直在使用i* pj的方式标记合数,没有跳跃,所以合数一定会被标记