1. 程式人生 > >埃拉託斯特尼篩法詳解及實現

埃拉託斯特尼篩法詳解及實現

埃拉託斯特尼篩法是一個快速獲取小於數X的所有素數集合的演算法。
首先我們要明確,假設一個合數x能表示為兩個數的乘積,他必定有一個小於等於sqrt(x)的因子,這可以用歸謬證明法證明。如果兩個因子都大於sqrt(x),那麼乘積大於x,這和假設矛盾。
所以,判斷一個數x是否是合數,只要依次除以2至sqrt(x)間的素數,判斷是否整除即可。

埃拉託斯特尼篩法基於以下原理,給定一個素數n>1,kn是一個合數(k>1),例如n=3,那麼6,9,12,15…都是合數。

以100為例,我們先建立一個100個數字的陣列。
先使用最小的素數2,將所有2的倍數(除2本身)標記為合數。
接下來2+1的數是3,此時檢查3是不是素數,檢查標記,發現沒有被標記為合數(因為不是2的倍數),所以再將所有3的倍數標記為合數。
下一個數是4,發現他已經被標記為合數,所以他可以表示小於4的素數的乘積2*2,所以4的倍數必定含有因子2,所以所有4的倍數已經全部被標記過,直接跳過4。
下一個數是5,沒有被標記為合數,把所有小於100的5的倍數標記為合數
………這樣一直計算到sqrt(100),即10。
那麼為什麼不標記大於10的數例如11呢?因為所有的倍數已經被標記過了,例如22,33,44,55…分別有因子2,3,2,5,大於10的倍數,例如11*11已經超過max了,參見最上面的推論
注意這裡有一個優化點,很多書籍上或者教程上都沒有說出來,只要標記大於本身的倍數就行了,例如5,只要標記5*5,5*6,5*7…為合數,因為5*2,5*3,5*4…已經被之前出現的數的倍數標記過了

  static List<Integer> findPrimes(int max) {

        List<Integer> list = new ArrayList<>();
        //為了保持索引值與數值一致,看上去更清晰,先加一個0
        list.add(0);
        for (int i = 1; i <= max; i++) {
            list.add(i);
        }
        //1不是素數,去掉
        list.set(1, 0);

        int
s = (int) Math.sqrt(max); //遍歷小於sqrt(max)的數 for (int i = 2; i <= s; i++) { //先判斷是不是素數 if (list.get(i) != 0) { //這裡直接忽略了小於i的倍數,因為之前肯定已經出現過了。(這個優化很多別的地方都沒有看到) int a = i * i; while (a <= max) { list
.set(a, 0); a += i; } } } return list.stream().filter(i -> i != 0).collect(Collectors.toList()); }

這樣,最終就可以獲取小於等於x的所有素數