我正在尝试制作一个体面的Java程序,从1到N生成素数(主要用于Project Euler问题)。
目前,我的算法如下:
初始化一个布尔数组(或者如果N足够大则初始化一个比特阵列)所以它们都是假的,并且存有一组用于存储素数的整数。
设置一个整数,s等于最低素数(即2)
s是< = sqrt(N)
在数组/位阵列中将s的所有倍数(从s ^ 2开始)设置为true。
在array / bitarray中找到下一个最小的索引,该索引为false,将其用作s的新值。
ENDWHILE。
遍历数组/ bitarray,对于每个false的值,将相应的索引放在primes数组中。
现在,我试过跳过不是6k + 1或6k + 5形式的数字,但这只能让我加速~2倍,而我看到的程序比我的程序运行速度快(虽然使用非常复杂的代码),例如here
我可以做些什么来改进?
编辑:好的,这是我的实际代码(对于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毫秒。
答案 0 :(得分:6)
当s是&lt; = sqrt(N)
时
人们经常在这种算法中犯的一个错误就是不预先计算平方根。
while (s <= sqrt(N)) {
比
慢得多int limit = sqrt(N);
while (s <= limit) {
但总的来说, Eiko 在他的评论中是正确的。如果您希望人们提供低级优化,您必须提供代码。
更新好的,现在关于您的代码。
您可能会注意到代码中的迭代次数比“l”略大。 (你可以把计数器放在第一个'for'循环中,它只会大2-3倍)显然,你的解决方案的复杂性不能低于O(l)(你不能低于'l) '迭代)。
真正有用的是有效地访问内存。请注意,写这篇文章的人试图减少存储空间,不仅仅是因为他的内存贪婪。制作紧凑型阵列可以让您更好地使用缓存,从而提高速度。
我刚用int []替换了boolean []并立即获得了x2速度增益。 (和8倍内存)我甚至没有尝试有效地做到这一点。
<强> UPDATE2 强>
这很简单。您只需将每个作业a[i] = true
替换为a[i/32] |= 1 << (i%32)
,将每个阅读操作a[i]
替换为(a[i/32] & (1 << (i%32))) != 0
。显然boolean[] a
和int[] a
一样。
从第一次替换后,应该清楚它是如何工作的:如果f(i)
为真,那么在1
的整数a[i/32]
位置i%32
位int
(如你所知,Java中的i/32
正好有32位。)
您可以更进一步,将i >> 5
替换为i%32
,将i&31
替换为1 << j
。您还可以为阵列中0到31之间的每个j预计算所有{{1}}。
但遗憾的是,我不认为在Java中你可以接近C。更不用说,那家伙使用了许多其他棘手的优化,我同意如果他发表评论,他的价值会更高。
答案 1 :(得分:3)
答案 2 :(得分:2)
在跳过不是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;
}
答案 3 :(得分:2)
以下内容来自我的Euler图书馆......它是Eratosthenes筛子的一个微小的变化......我不确定,但我认为它叫做Euler Sieve。
1)它使用BitSet(所以内存的1/8) 2)仅使用奇数位的位集...(另外1/2因此1/16)
注意:内部循环(对于倍数)从“n * n”而不是“2 * n”开始,并且增量“2 * n”的倍数仅被划掉....因此加速。< / p>
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;
}
答案 4 :(得分:1)
你可以在检测它们时执行“将相应的索引放在primes数组中”的步骤,然后在数组中运行,但这就是我现在能想到的所有内容。
答案 5 :(得分:1)
我最近使用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快得多。
答案 6 :(得分:0)
我敢打赌,在处理比特时,java的表现很糟糕...... 从算法上讲,你指出的链接应该足够了
答案 7 :(得分:0)
您是否尝试过谷歌搜索,例如为“java素数”。我做了并挖掘了这个简单的改进:
http://www.anyexample.com/programming/java/java_prime_number_check_%28primality_test%29.xml
当然,你可以在谷歌找到更多。
答案 8 :(得分:0)
以下是我对Erastothenes筛选的代码,这实际上是我能做的最有效的:
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;
}