素數的快速判定

                                           判斷素數的方法

定義:“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 \sqrt{20}根號20 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裏面紀錄的素數,升序來當做要消去合數的最小素因子。

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章