Java中的Eratosthenes筛:一个难题和一些优化

时间:2010-11-11 15:07:16

标签: java primes sieve-of-eratosthenes

我在Java中快速实现了SoE算法(最后的代码)。我的双核AMD处理器的输出是:

Allocation:      31
Meat:            10140
Listing:         10171
Preparing end:   10187
  • “肉类”部分按预期消耗最长时间。

  • 我的一个观察结果是使用Math.pow(variable, 2)(variable * variable)慢。我认为除了函数跳转之外,可能还有其他开销。

  • Math.pow(x,2)是否对2,3的幂等进行了优化?我问,因为有一些用户贡献的Java库,其乘法算法比Java的原生库快得多。

以下是我的问题:

  • 您可以向Meat部分建议哪些算术优化?有什么办法可以完全避开模数运算符吗?

  • 当start == end时,该功能不起作用。如果我筛选(4,4),返回的数组长度为1:[4]。我究竟做错了什么?它应该返回[](基本上是新的int(0))。

  • 您知道哪些快速数字/数学相关的Java库?

感谢阅读。最后这是我写的代码:不是GangOfFour / TopCoder的质量,但也不是太可怜(我希望!,SO的代码格式有点......很奇怪?):

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Sieve {

    public static void main(String[] args) {

        /* small test */
        int[] primes = sieve(1, 1000000);
    }

    /**
     * returns an array of prime integers
     * in the given range
     * 
     * @param start     range start
     * @param end       range end
     * @return
     */
    private static int[] sieve(int start, int end) {

        long startTime = System.currentTimeMillis();

        /* some basic range checks */
        if(end < start || start < 1 || end  < 1) {
            throw new ArithmeticException("Messed up input");
        }

        /* generate ints within range */
        int[] naturals = new int[end-start+1];
        for (int j = 0; j < end - start + 1; j++) {
            naturals[j] = start + j;
        }
        System.out.println("Allocation: \t" + (System.currentTimeMillis() - startTime));

        /* init running prime to start, and increment until
         * running prime squared is greater than the end
         */
        for (int runningPrime = (start == 1 ? 2: start); end > runningPrime*runningPrime; runningPrime++) {
            for (int i = runningPrime; i < naturals.length; i++) {
                if(-1 != naturals[i]) {
                    if(naturals[i] % runningPrime == 0) {
                        naturals[i] = -1;
                    }
                }
            }
        }
        System.out.println("Meat: \t\t" + (System.currentTimeMillis() - startTime));

        if(naturals[0] == 1) {
            naturals[0] = -1;
        }

        /* list primes */
        List list = new ArrayList();
        for (int i = 0; i < naturals.length; i++) {
            if(-1 != naturals[i])
                list.add(naturals[i]);
        }
        System.out.println("Listing: \t" + (System.currentTimeMillis() - startTime));

        /* create the return int array */
        int[] primes = new int[list.size()];
        int k = 0;
        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
            primes[k++] = ((Integer) iterator.next()).intValue();
        }

        System.out.println("Preparing end: \t" + (System.currentTimeMillis() - startTime));
        return primes;
    }
}

感谢所有反馈。这是下面的固定版本(直到有人设法再次破坏它:)

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Sieve {
    public static void main(String[] args) {
        /* small test */
        int[] primes = sieve(2, 5);
        System.out.println("Number of primes: " + primes.length);
        for (int i : primes) {
            System.out.println(i);
        }
    }

/**
 * returns an array of prime integers
 * in the given range
 * 
 * @param start     range start
 * @param end       range end
 * @return
 */
private static int[] sieve(int start, int end) {

    long startTime = System.currentTimeMillis();

    /* some basic range checks */
    if(end < start || start < 1 || end  < 1) {
        throw new ArithmeticException("Messed up input");
    }

    /* generate ints within range */
    int[] naturals = new int[(int)Math.floor((end-start+1) / 2) + 1];
    int allocator = 0;
    for (int j = 0; j < end - start + 1; j++) {
        if(!((start + j) % 2 == 0)) {
            naturals[allocator++] = start + j;
        }
    }
    System.out.println("Allocation: \t" + (System.currentTimeMillis() - startTime));

    /* init running prime to 2, and increment until
     * running prime squared is greater than the end
     */
    for (int runningPrime = 2; end >= runningPrime*runningPrime; runningPrime++) {
        for (int i = 0; i < naturals.length; i++) {
            if(-1 != naturals[i]) {
                if(naturals[i] != runningPrime && naturals[i] % runningPrime == 0) {
                    naturals[i] = -1;
                }
            }
        }
    }
    System.out.println("Meat: \t\t" + (System.currentTimeMillis() - startTime));

    if(naturals[0] == 1) {
        naturals[0] = -1;
    }

    /* list primes */
    List list = new ArrayList();
    for (int i = 0; i < naturals.length; i++) {
        if(-1 != naturals[i])
            list.add(naturals[i]);
    }
    System.out.println("Listing: \t" + (System.currentTimeMillis() - startTime));

    /* create the return int array */
    int size = list.size();
    int k = 0;

    /* tricky tricky :) */
    if(start <= 2) {
        size += 1;
        k = 1;
    }

    int[] primes = new int[size];

    if(start <= 2) {
        primes[0] = 2;
    }

    for (Iterator iterator = list.iterator(); iterator.hasNext();) {
        primes[k++] = ((Integer) iterator.next()).intValue();
    }

    System.out.println("Preparing end: \t" + (System.currentTimeMillis() - startTime));
    return primes;
    }
}

5 个答案:

答案 0 :(得分:3)

你可以通过重写内循环来避免模数:

        for (int i = runningPrime; i < naturals.length; i++) {
            if(-1 != naturals[i]) {
                if(naturals[i] % runningPrime == 0) {
                    naturals[i] = -1;
                }
            }
        }

as

        for (int i = runningPrime; i < naturals.length; i+=runningPrime) {
             naturals[i] = -1;
        }

我也有点担心包含start参数会使事情复杂化(考虑sieve(4, 10)的情况)。

答案 1 :(得分:1)

假设我没有错过我写的东西:

 for(int runningPrime = (start == 1 ? 2: start); end > runningPrime*runningPrime;
 runningPrime++) 

as

int limit = Math.sqrt(end);
for(int runningPrime = (start == 1 ? 2: start); runningPrime < limit; 
runningPrime++) 

防止每次迭代不必要的乘法。我也只会填补 奇数的数组,有效地将其长度减半。

答案 2 :(得分:1)

你的解决方案不是 Eratosthenes的Sieve。这很明显,因为您在代码中使用modulo运算符;一个适当的Eratosthenes筛子只使用内环中的加法,而不是除法或模数。以下是Eratosthenes筛选的简单版本,它从BitSet导入LinkedListjava.util并返回小于 n 的素数的LinkedList:

public static LinkedList sieve(int n)
{
    BitSet b = new BitSet(n);
    LinkedList ps = new LinkedList();

    b.set(0,n);

    for (int p=2; p<n; p++)
    {
        if (b.get(p))
        {
            ps.add(p);
            for (int i=p+p; i<n; i+=p)
            {
                b.clear(i);
            }
        }
    }

    return ps;
}

基本想法是创建筛子BitSet b ),每个项目最初设置为Prime(表示为一组比特),遍历筛子寻找并报告每个连续的素数,并且当发现一个人通过标记它Composite(表示为清除位)从筛子中击出它的所有倍数时。通过加法而不是除法找到倍数,并且内循环仅包括加法,位清除操作,查找筛子末端的比较,以及跳回到循环开头的所有内容,因此它非常快。

有一些优化可以让Eratosthenes的Sieve运行得更快,但这应该足以让你开始。当您准备好了更多时,我在我的博客上谦虚地推荐this essay

如果你想要一个不从零开始的范围内的素数,你需要一个分段的 Eratosthenes筛子。我在Stack Overflow上讨论了分段的Eratosthenes Sieve previously,并在我的博客上讨论了discuss it

答案 3 :(得分:0)

只填充赔率(2除外),通过runningPrime递增,并且已经建议丢失可分性检查,可能是最重要的优化。

Java的Math.pow用于双打!它没有优化平方,大多数情况下它会立即将2重铸为双倍。

答案 4 :(得分:0)

在我看来,在开始优化之前,你应该修复两个严重的错误。

我将您的代码编译为Java程序,然后尝试计算

sieve(1, 9)

sieve(4,10);

第一种情况正常,但9被认为是素数。 9的平方根是素数,但是你的循环条件会在你到达之前停止筛分。

在第二种情况下,所谓的素数是4,5,6,7,8,9,10。这是因为你跳过了范围开始以下任何素数的筛选。那,我恐怕是一个优化太过分了: - )