判斷素數的方法
定義:“1不是質數。質數又稱素數。一個大於1的自然數,除了1和它自身外,不能被其他自然數整除的數叫做質數;否則稱爲合數。所以1不是質數。質數的個數是無窮的。歐幾里得的《幾何原本》中有一個經典的證明。它使用了證明常用的方法:反證法。”約數只有1和本身的整數稱爲質數,或稱素數。
Level 1:我們最先學的簡單粗暴的質數函數判定
就像是這樣:
bool is_prime(int x)
{
if(x==0 || x==1) return false;//0和1不是素數
if(x==2) return true;//2是素數
for(int i=2;i*i<=x;i++)枚舉2~sqrt(i)中的因數
{
if(x%i==0) return false;//如果這個數不包括自己和1之外有因數,說明這個數不是素數
}
return true;//如果除了1和自己外沒有因數,說明是素數
}
這種方法的時間複雜度很大,一般在OI中不建議使用
1.直觀判斷法:
最直觀的方法,根據定義,因爲質數除了1和本身之外沒有其他約數,所以判斷n是否爲質數,根據定義直接判斷從2到n-1是否存在n的約數即可。java代碼如下:
import java.util.*;
public class Main5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int ok = 0;
for (int i = 2; i < n; i++) {
if (n % i == 0) {
ok = 1;
break;
}
}
if (ok == 0) {
System.out.println("素數");
} else {
System.out.println("不是素數");
}
}
}
2.直觀判斷法改進
上述判斷方法,明顯存在效率極低的問題。對於每個數n,其實並不需要從2判斷到n-1,我們知道,一個數若可以進行因數分解,那麼分解時得到的兩個數一定是一個小於等於sqrt(n),一個大於等於sqrt(n),據此,上述代碼中並不需要遍歷到n-1,遍歷到sqrt(n)即可,因爲若sqrt(n)左側找不到約數,那麼右側也一定找不到約數。java代碼如下:
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
public class Main3{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int ok=0;
for (int i = 2; i <=Math.sqrt(n) ; i++) {
if(n%i==0) {
ok=1;
break;
}
}
if(ok==0) {
System.out.println("素數");
}else {
System.out.println("不是素數");
}
}
}
例題:
代碼:
import java.math.BigInteger;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
long n = sc.nextLong();
int jj = 0;
long sum = 1l;
ff:for (int i = 2;; i++) {
for (int j = 2; j <=Math.sqrt(i); j++) {
if (i % j == 0) {
jj++;
}
}
if (jj == 0) {
sum = (sum*i)%50000;
n--;
if(n==0) {
break ff;
}
}
jj = 0;
}
System.out.println(sum);
}
}
}
測評結果:
分析:判斷有一個數被除開之後應該立即跳出循環!!break!!
代碼改進:
import java.math.BigInteger;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
long n = sc.nextLong();
int jj = 0;
long sum = 1l;
ff:for (int i = 2;; i++) {
for (int j = 2; j <=Math.sqrt(i); j++) {
if (i % j == 0) {
jj++;
break;
}
}
if (jj == 0) {
sum = (sum*i)%50000;
n--;
if(n==0) {
break ff;
}
}
jj = 0;
}
System.out.println(sum);
}
}
}
測評結果:
例題(杭電oj1215):
分析及題意:求出所有約數之和,一個數若可以進行因數分解,那麼分解時得到的兩個數一定是一個小於等於sqrt(n),一個大於等於sqrt(n),據此,上述代碼中並不需要遍歷到n-1,遍歷到sqrt(n)即可,因爲若sqrt(n)左側找不到約數,那麼右側也一定找不到約數。所以只要找出左面的約數,右面的也就知道了。注意9=3*3
例如20的約數是:
1 2 4 5 10; 遍歷到2 ,右面的等於20/2=10 ,遍歷到4,右面的等於20/4=5。
代碼實現:
import java.util.Scanner;
public class Main2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int n = sc.nextInt();
while (n-- > 0) {
int a = sc.nextInt();
long sum = 0l;
for (int j = 1; j <= Math.sqrt(a); j++) {
if (a % j == 0) {
sum += j;
if (j * j != a) {
if (j != 1) {
sum += a / j;
}
}
}
}
System.out.println(sum);
}
}
}
}
總結:
一個數若可以進行因數分解,那麼分解時得到的兩個數一定是一個小於等於sqrt(n),一個大於等於sqrt(n),據此,上述代碼中並不需要遍歷到n-1,遍歷到sqrt(n)即可,因爲若sqrt(n)左側找不到約數,那麼右側也一定找不到約數。
判斷前2到n個數的所有素數,其中最小的數字2是素數。將表中所有2的倍數都劃去。表中剩餘的最小數字是3,它不能被更小的數整除,所以是素數。再將表中所有3的倍數全都劃去。依次類推,如果表中剩餘的最小數字是m時,m就是素數。然後將表中所有m的倍數全部劃去。像這樣反覆操作,就能依次枚舉n以內的素數
Level 2:埃氏篩法
-
基本思想 :從2開始,將每個質數的倍數都標記成合數,以達到篩選素數的目的。
- 代碼 :
int visit[maxn]; void Prime(int n) { memset(visit,0,sizeof(visit));//初始化都是素數 visit[0]=visit[1]=1;//0和1不是素數 for(int i=2;i<=n;i++) { if(!visit[i])//如果i是素數,讓i的所有倍數都不是素數 { for (int j=i;j<=n;j+=i) { visit[j] = 1; } } }
但是埃氏篩法有缺陷 :對於一個合數,有可能被篩多次。例如30=2×15=3×10= 5×6……那麼如何確保每個合數只被篩選一次呢?我們只要用它的最小質因子來篩選即可,這便是歐拉篩法。
Level 3:歐拉篩法
-
基本思想 :在埃氏篩法的基礎上,讓每個合數只被它的最小質因子篩選一次,以達到不重複的目的。
- 代碼 :
int prime[maxn];
int visit[maxn];
void Prime(int n){
memset(visit,0,sizeof(visit));
memset(prime,0,sizeof(prime));
for(int i=2;i<=n;i++) {
if(!visit[i]) {
prime[++prime[0]]=i;//記錄素數,這個prime[0]相當於cnt,用來計數
}
for(int j=1;j<=prime[0] && i*prime[j]<=maxn;j++) {
visit[i*prime[j]] = 1;
if (i%prime[j] == 0) {
break;
}
}
}
}
對於visit[i*prime[j]]=1的解釋:這裏不是用i的倍數來消去合數,而是把 prime裏面紀錄的素數,升序來當做要消去合數的最小素因子。