[英]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[] a
和int[] 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。 更不用說,那個人使用了許多其他棘手的優化,而且我同意,如果他發表評論,他本來會價值更高。
您是否還跳過了非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.