【问题标题】:Sieve Eratosthenes greater than int筛选大于 int 的 Eratosthenes
【发布时间】:2015-09-04 04:44:07
【问题描述】:

我想找出 100 亿以下的所有素数。这是 int 可以容纳的 5 倍(这是数组的限制,无论类型如何)。尝试一次分配超过 12 亿个会导致堆空间不足错误。我尝试使用 List 而不是布尔数组,但 arrayLists 的 set element 方法只能索引到 int。让我烦恼的是,很快进入筛子的元素少于整数个没有被划掉。一种应该有效的方法是创建一个由 10 个数组组成的分区并将它们粉碎在一起......但这会很丑陋。如果您对解决此问题的优雅方法有任何建议,请告诉我。 (除了使用 Python 哈哈)。我已经有一个 n^2/2 蛮力实现,但这需要很长时间才能运行,所以我真的想尽可能快地解决这个问题。我的高达 12 亿的 Sieve 实现如下:

public class SieveEratosthenes {
private boolean[] nums;
public static void main(String[] args) {
    int n = 1000000;
    SieveEratosthenes s = new SieveEratosthenes(n);
    for(int i=0;i<s.nums.length;i++){
        if(s.nums[i]){
            System.out.println(i);
        }
    }
}

public SieveEratosthenes(int max){
    sieve(max);
}

private boolean[] sieve(int max){
    nums = new boolean[max+1];
    initFlags();
    for(int i=2;i*i<max;i++){
        for(int j=i*i;j<=max;j+=i){//cross off non-primes
            nums[j]=false;
        }
    }
    return nums;
}
private void initFlags(){
    if(nums != null&&nums.length>1){
        nums[0]=false;
        nums[1]=false;
        nums[2]=true;
    }
    for(int i=3;i<nums.length;i++){
        nums[i]=true;
    }
}

public List<Long> sieveToList(){
    List<Long> sieveList = new ArrayList();
    for(int i=0;i<nums.length;i++){
        if(nums[i]){
            sieveList.add((long)i);
        }
    }
    return sieveList;
}

【问题讨论】:

  • 使用Java的BigInteger类,痛苦会少很多!
  • 您只能在 HashSet 中放入素数。这样可以节省大量内存。
  • @Am_I_Helpful 提问者没有使用ints 作为素数列表的元素,而是使用boolean[],其中第 n 个元素描述了 n。问题是数组索引是int 值,因此只能描述直到MAX_INT 的数字的素数。除非存在由BigInteger 值索引的列表结构,否则在此实现中使用BigInteger 不能替换int
  • @Alden - 这就是我的建议,删除数组练习,带上一个列表或其他东西,然后实现相同的程序。那会少很多痛苦。我建议他改变实施方式!
  • stackoverflow.com/questions/8804435/… 可能是OpenBitSet(参见answer)。另请注意,您可以利用 2^n 永远不是素数的事实

标签: java algorithm


【解决方案1】:

这是您可以使用的一种方法:

  • 使用 10^7 整数或任何适合您的大小的筛子。
  • 然后,对于 sieve 的每个实现,最后,将所有计算的素数保存在您熟悉的任何数据结构中(ArrayList 可以)。
  • 现在,这样做 1000 次,使用循环(当然)每次,您的筛子都会计算下一个 10^7 范围内的素数。因此,在第一次迭代中,将计算 0-10^7 的所有素数。然后,从 10^7+1 到 2*10^7 等等。

PS:如果你想要代码,我会为你做,但我建议你尝试一次。我可能错了,但我认为这种方法就是他们所说的segmented sieve

【讨论】:

  • 是的,我确实考虑过将其拆分为多个段,但我希望有一种方法可以使用单一数据结构。在这种方法中,我一定不会一次存储它们。我认为一旦计算出其中一个段,我会将 boolean[] 卸载到 arrayList 中,同时从布尔位置转换为素数的值。这样只有素数存储在 arrayList 中,并且将有足够的空间,因为在 100 亿以下的 # 中只有 5.4% 是素数。然后我会使用 arrayList 开始划掉下一段,然后按照你说的循环。
  • 是的。如果您对该实现有任何问题,请在此处发布,我想我会提供帮助。
  • 嘿,你在哪里发现 10^10 以下的 # 中有 5.4% 是素数?一个很好的近似值由 (N / ln(N)) 个 0 到 N 范围内的素数给出。(ln(N) 是 N 的以 e 为底的对数),得到 4.34%。
  • 10^8,实际上。当我第一次解决这个蛮力问题时,我写下了 10^8 以下的所有 10^x 中有多少个素数,这样我就可以估计运行 lol 需要多少小时!但是是的,想想 10^2 中有多少个素数,看看它是如何逐渐减少的。
  • 是的,如果 10^8 是 5.4,我认为 4.34 代表 10^10。
【解决方案2】:

您可能不应该为此使用数组。正如你所说,它们不太适合非常大的集合。该算法的一个合理近似是在您检查每个素数时“划掉”,通过测试它是任何先前素数的倍数。这个性能我还没分析。

class Sieve {
    private long current = 2;
    private final List<Long> primes = new ArrayList<>();

    public long nextPrime() {
        while (primes.stream().anyMatch(p -> current % p == 0))
            current++;
        primes.add(current);
        return current;
    }
}

【讨论】:

  • 刚刚测试了你的。我很遗憾地说,虽然那是一段漂亮的代码,但我的筛法和蛮力方法几乎都能立即响应计算 100000 以下的素数。但你的方法需要 3 秒。所以我不确定是并行流开销还是“anyMatch”部分需要很长时间。我在 java 8 中做的不多,很高兴看到一个漂亮的 lambda 方法。我认为它会在未来的问题上派上用场。再次感谢!
  • @woodlumhoodlum 我在查看它的性能时发现了一些有趣的东西。在我的(多核)mac 上,stream 的运行速度是parallelStream 的 4 倍。我将不得不对此进行调查。我想我们不应该太惊讶它比基于数组的解决方案慢,因为流和集合确实有开销。
  • 在我的上更改为 stream() 使其始终在 2 秒内运行。节省一秒,赚一秒。我有一个中档 i5。
【解决方案3】:

如果内存不是问题,请继续使用数组,因为它更快。如果内存成为问题,我建议研究一下 BitSets。

虽然 Java 数据结构被限制(据我所知)最大 int 大小约为 20 亿,但您可以创建自己的数据结构。一个非常简单的解决方案是创建一个类,将您请求的大小拆分为给定最大 int 长度的几个数组或位集,然后通过您输入的长索引输入自动访问它们。我希望这是有道理的。如果您有任何问题,请告诉我!

【讨论】:

猜你喜欢
  • 2015-03-19
  • 1970-01-01
  • 1970-01-01
  • 2022-11-28
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 2013-04-11
  • 1970-01-01
相关资源
最近更新 更多