簡體   English   中英

改進主篩算法

[英]Improving a prime sieve algorithm

我正在嘗試制作一個體面的Java程序,該程序生成從1到N的質數(主要用於Euler問題)。

目前,我的算法如下:

初始化一個布爾數組(如果N足夠大,則初始化一個位數組),以便它們全為假,並初始化一個整數數組來存儲找到的素數。

設置一個整數s等於最低質數(即2)

當s <= sqrt(N)

在數組/位數組中將s的所有倍數(從s ^ 2開始)設置為true。

在數組/位數組中找到下一個最小的索引,該索引為false,將其用作s的新值。

最后

遍歷數組/位數組,對於每個錯誤的值,將相應的索引放入素數數組。

現在,我嘗試跳過非6k +1或6k + 5形式的數字,但這只能使我快2倍,而我看到程序的運行速度比我的快幾個數量級(盡管非常費時。代碼),例如此處的一個

我該怎么做才能改善?

編輯:好的,這是我的實際代碼(對於1E7的N):

int l = 10000000, n = 2, sqrt = (int) Math.sqrt(l);
boolean[] nums = new boolean[l + 1];
int[] primes = new int[664579];

while(n <= sqrt){
    for(int i = 2 * n; i <= l; nums[i] = true, i += n);
    for(n++; nums[n]; n++);
}

for(int i = 2, k = 0; i < nums.length; i++) if(!nums[i]) primes[k++] = i;

在我的2.0GHz機器上運行約350毫秒。

當s <= sqrt(N)
人們在此類算法中經常犯的一個錯誤不是預先計算平方根。

while (s <= sqrt(N)) {

比慢很多

int limit = sqrt(N);
while (s <= limit) {

但總的來說, Eiko的評論是正確的。 如果您希望人們提供底層優化,則必須提供代碼。

更新 OK,現在有關您的代碼。

您可能會注意到代碼中的迭代次數僅比“ l”大。 (您可以將計數器放在第一個“ for”循環中,它只會大2-3倍)而且,很明顯,解決方案的復雜度不能小於O(l)(不能小於“ l” '迭代)。

真正起作用的是有效訪問內存。 請注意,撰寫該文章的人試圖減小存儲大小,不僅僅是因為他的內存貪婪。 制作緊湊的陣列使您可以更好地利用緩存,從而提高速度。

我只是將boolean []替換為int [],並立即獲得了x2的速度提升。 (和8倍內存),我什至沒有嘗試有效地做到這一點。

更新2
這很簡單。 您只需將每個賦值a[i] = true替換為a[i/32] |= 1 << (i%32)並將每個讀取操作a[i]替換為(a[i/32] & (1 << (i%32))) != 0 顯然是boolean[] aint[] a

從第一次替換開始,應該清楚它是如何工作的:如果f(i)為true,則在位置i%32處的整數a[i/32]有一個1 (Java中的int恰好有32位,如你所知)。

您可以進一步將i/32替換為i >> 5 ,將i%32替換為i&31 您還可以為數組中0到31之間的每個j預計算所有1 << j

但是可悲的是,我認為在Java中您無法接近C。 更不用說,那個人使用了許多其他棘手的優化,而且我同意,如果他發表評論,他本來會價值更高。

使用BitSet將占用更少的內存。 Sieve算法相當簡單,因此您可以簡單地“設置” BitSet上的位位置,然后進行迭代以確定素數。

您是否還跳過了非6k + 1和6k + 5形式的數字時使數組變小了? 我只測試了忽略2k形式的數字,這使我的速度提高了約4倍(440毫秒-> 120毫秒):

int l = 10000000, n = 1, sqrt = (int) Math.sqrt(l);
int m = l/2;
boolean[] nums = new boolean[m + 1];
int[] primes = new int[664579];
int i, k;

while (n <= sqrt) {
  int x = (n<<1)+1;
  for (i = n+x; i <= m; nums[i] = true, i+=x);
  for (n++; nums[n]; n++);
}

primes[0] = 2;
for (i = 1, k = 1; i < nums.length; i++) {
  if (!nums[i])
    primes[k++] = (i<<1)+1;
}

以下是我的Euler項目庫中的內容...它是Eratosthenes篩子的細微變化...我不確定,但我認為它叫Euler篩子。

1)它使用一個BitSet(因此是內存的1/8)2)僅使用該位用於奇數...(另一個1/2則為1/16)

注意:內部循環(用於倍數)從“ n * n”而不是“ 2 * n”開始,並且增量“ 2 * n”的倍數也只會被舍掉....因此加快了速度。

private void beginSieve(int mLimit) 
{ 
    primeList = new BitSet(mLimit>>1); 
    primeList.set(0,primeList.size(),true); 

    int sqroot = (int) Math.sqrt(mLimit); 
    primeList.clear(0); 
    for(int num = 3; num <= sqroot; num+=2) 
    { 
        if( primeList.get(num >> 1) ) 
        { 
            int inc = num << 1;
            for(int factor = num * num; factor < mLimit; factor += inc) 
            { 
                //if( ((factor) & 1) == 1) 
                //{ 
                    primeList.clear(factor >> 1); 
                //} 
            } 
        } 
    } 
} 

這是檢查數字是否為質數的功能...

public boolean isPrime(int num) 
{ 
    if( num < maxLimit)
    {
        if( (num & 1) == 0) 
            return ( num == 2); 
        else 
            return primeList.get(num>>1);
    }
    return false;
} 

我最近使用BitSet為其編寫了一個簡單的篩子實現方法(每個人都不願意,但這是有效存儲大量數據的最好的現成方法)。 對我來說,性能似乎不錯,但我仍在努力改進它。

public class HelloWorld {
    private static int LIMIT = 2140000000;//Integer.MAX_VALUE broke things.
    private static BitSet marked;

    public static void main(String[] args) {
         long startTime = System.nanoTime();
        init();
        sieve();
         long estimatedTime = System.nanoTime() - startTime;
        System.out.println((float)estimatedTime/1000000000); //23.835363 seconds
        System.out.println(marked.size()); //1070000000 ~= 127MB
    }

    private static void init()
    {
        double size = LIMIT * 0.5 - 1;
        marked = new BitSet();
        marked.set(0,(int)size, true);
    }

    private static void sieve()
    {
        int i = 0;
        int cur = 0; 
        int add = 0;
        int pos = 0;

        while(((i<<1)+1)*((i<<1)+1) < LIMIT)
        {
            pos = i;
            if(marked.get(pos++))
            {
                cur = pos;
                add = (cur<<1);
                pos += add*cur + cur - 1;
                while(pos < marked.length() && pos > 0)
                {
                    marked.clear(pos++);
                    pos += add;
                }
            }
            i++;
        }
    }

    private static void readPrimes()
    {
        int pos = 0;
        while(pos < marked.length())
        {
            if(marked.get(pos++))
            {
                System.out.print((pos<<1)+1);
                System.out.print("-");
            }
        }
    }
}

使用較小的LIMIT(例如,10,000,000耗時0.077479s),我們得到的結果要比OP快得多。

您可以在檢測到它們時執行“將相應的索引放入primes數組中”的步驟,對數組進行遍歷,但這就是我現在能想到的。

我敢打賭,當處理位時,java的性能非常糟糕...從算法上講,您指出的鏈接應該足夠了

您是否嘗試過谷歌搜索,例如“ java質數”。 我做了並挖掘了這個簡單的改進:

http://www.anyexample.com/programming/java/java_prime_number_check_%28primality_test%29.xml

當然,您可以在Google找到更多信息。

這是我的橡膠網篩的代碼,這實際上是我能做的最有效的:

final int MAX = 1000000;
int p[]= new int[MAX];
p[0]=p[1]=1;
int prime[] = new int[MAX/10];
prime[0]=2;
void sieve()
{
    int i,j,k=1;
    for(i=3;i*i<=MAX;i+=2)
    {
        if(p[i])
            continue;
        for(j=i*i;j<MAX;j+=2*i)
            p[j]=1;
    }
    for(i=3;i<MAX;i+=2)
    {
        if(p[i]==0)
            prime[k++]=i;
    }
    return;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM