22

注意:下面的版本 2 使用 Eratosthenes 筛。有几个答案对我最初提出的问题有所帮助。我选择了埃拉托色尼筛法,实施了它,并适当地改变了问题的标题和标签。感谢所有帮助过的人!

介绍

我写了这个花哨的小方法,它生成一个包含小于指定上限的素数的 int 数组。它工作得很好,但我有一个担心。

方法

private static int [] generatePrimes(int max) {
    int [] temp = new int [max];
    temp [0] = 2;
    int index = 1;
    int prime = 1;
    boolean isPrime = false;
    while((prime += 2) <= max) {
        isPrime = true;
        for(int i = 0; i < index; i++) {
            if(prime % temp [i] == 0) {
                isPrime = false;
                break;
            }
        }
        if(isPrime) {
            temp [index++] = prime;
        }
    }
    int [] primes = new int [index];
    while(--index >= 0) {
        primes [index] = temp [index];
    }
    return primes;
}

我的顾虑

我担心的是我创建的数组对于该方法将返回的最终元素数量来说太大了。问题是我不知道正确猜测小于指定数字的素数数量的好方法。

重点

这就是程序使用数组的方式。这是我想要改进的地方。

  1. 我创建了一个足够大的临时数组,可以容纳每个小于限制的数字。
  2. 我生成质数,同时计算我生成了多少。
  3. 我制作了一个新的数组,它的维数正确,可以只保存素数。
  4. 我将每个素数从巨大的数组复制到正确维度的数组中。
  5. 我返回仅包含我生成的素数的正确维度的数组。

问题

  1. temp[]我可以将具有非零元素的整个块(一次)复制 到primes[] 而不必遍历两个数组并一个一个地复制元素吗?
  2. 是否有任何数据结构的行为类似于可以随着元素的添加而增长的基元数组,而不是在实例化时需要维度?与使用基元数组相比,性能损失是多少?

版本 2(感谢Jon Skeet):

private static int [] generatePrimes(int max) {
    int [] temp = new int [max];
    temp [0] = 2;
    int index = 1;
    int prime = 1;
    boolean isPrime = false;
    while((prime += 2) <= max) {
        isPrime = true;
        for(int i = 0; i < index; i++) {
            if(prime % temp [i] == 0) {
                isPrime = false;
                break;
            }
        }
        if(isPrime) {
            temp [index++] = prime;
        }
    }
    return Arrays.copyOfRange(temp, 0, index);
}

版本 3(感谢Paul Tomblin)使用Erastosthenes 筛法

private static int [] generatePrimes(int max) {
    boolean[] isComposite = new boolean[max + 1];
    for (int i = 2; i * i <= max; i++) {
        if (!isComposite [i]) {
            for (int j = i; i * j <= max; j++) {
                isComposite [i*j] = true;
            }
        }
    }
    int numPrimes = 0;
    for (int i = 2; i <= max; i++) {
        if (!isComposite [i]) numPrimes++;
    }
    int [] primes = new int [numPrimes];
    int index = 0;
    for (int i = 2; i <= max; i++) {
        if (!isComposite [i]) primes [index++] = i;
    }
    return primes;
}
4

13 回答 13

13

您通过将数组的每个元素与每个可能的因素进行比较来找到素数的方法非常低效。您可以通过一次对整个阵列进行埃拉托色尼筛法来极大地改进它。除了进行更少的比较之外,它还使用加法而不是除法。分工比较慢。

于 2009-02-25T14:57:58.907 回答
10

ArrayList<>埃拉托色尼筛

// Return primes less than limit
static ArrayList<Integer> generatePrimes(int limit) {
    final int numPrimes = countPrimesUpperBound(limit);
    ArrayList<Integer> primes = new ArrayList<Integer>(numPrimes);
    boolean [] isComposite    = new boolean [limit];   // all false
    final int sqrtLimit       = (int)Math.sqrt(limit); // floor
    for (int i = 2; i <= sqrtLimit; i++) {
        if (!isComposite [i]) {
            primes.add(i);
            for (int j = i*i; j < limit; j += i) // `j+=i` can overflow
                isComposite [j] = true;
        }
    }
    for (int i = sqrtLimit + 1; i < limit; i++)
        if (!isComposite [i])
            primes.add(i);
    return primes;
}

小于或等于素数上限的公式max(参见wolfram.com):

static int countPrimesUpperBound(int max) {
    return max > 1 ? (int)(1.25506 * max / Math.log((double)max)) : 0;
}
于 2009-02-26T02:22:31.147 回答
8

创建一个ArrayList<Integer>,然后int[]在最后转换为一个。

周围有各种第 3 方IntList(等)类,但除非你真的担心拳击几个整数的命中,否则我不会担心。

不过,您可以使用它Arrays.copyOf来创建新数组。您可能还想在每次需要时通过将大小翻倍来调整大小,然后在最后进行修剪。那基本上是在模仿这种ArrayList行为。

于 2009-02-25T14:58:52.100 回答
7

使用埃拉托色尼筛法的算法

public static List<Integer> findPrimes(int limit) {

    List<Integer> list = new ArrayList<>();

    boolean [] isComposite = new boolean [limit + 1]; // limit + 1 because we won't use '0'th index of the array
    isComposite[1] = true;

    // Mark all composite numbers
    for (int i = 2; i <= limit; i++) {
        if (!isComposite[i]) {
            // 'i' is a prime number
            list.add(i);
            int multiple = 2;
            while (i * multiple <= limit) {
                isComposite [i * multiple] = true;
                multiple++;
            }
        }
    }

    return list;
}

描述上述算法的图像(灰色单元格表示素数。由于我们最初将所有数字视为素数,因此整个网格最初是灰色的。)

在此处输入图像描述

图片来源:维基媒体

于 2013-12-22T13:57:48.740 回答
2

最简单的解决方案是返回Collections Framework的一些成员而不是数组。

于 2009-02-25T14:58:38.827 回答
2

您使用的是 Java 1.5 吗?为什么不退货List<Integer>和使用ArrayList<Integer>?如果您确实需要返回一个int[],您可以通过int[]在处理结束时将 List 转换为来实现。

于 2009-02-25T15:00:54.983 回答
1

正如 Paul Tomblin 所指出的,有更好的算法。

但是保持你所拥有的,并假设每个结果的对象太大:

您只会追加到数组中。因此,请使用相对较小的 int[] 数组。当它完全使用时,将其附加到列表并创建一个替换。最后将其复制到正确大小的数组中。

或者,猜测 int[] 数组的大小。如果它太小,用一个大小比当前数组大小大一点的 int[] 替换。这样做的性能开销将与大小成正比。(这在最近的 stackoverflow 播客中进行了简要讨论。)

于 2009-02-25T15:06:22.910 回答
1

现在你已经有了一个基本的筛子,注意内循环只需要 continue until temp[i]*temp[i] > prime

于 2009-02-25T15:13:05.953 回答
1

我有一个非常有效的实现:

  1. 我们不保留偶数,因此将内存使用量减半。
  2. 我们使用BitSet,每个数字只需要一位。
  3. 我们估计了区间上素数的上限,因此我们可以initialCapacity适当地设置数组的 。
  4. 我们不在循环中执行任何类型的除法。

这是代码:

public ArrayList<Integer> sieve(int n) {
    int upperBound = (int) (1.25506 * n / Math.log(n));
    ArrayList<Integer> result = new ArrayList<Integer>(upperBound);
    if (n >= 2)
        result.add(2);

    int size = (n - 1) / 2;
    BitSet bs = new BitSet(size);

    int i = 0;
    while (i < size) {
        int p = 3 + 2 * i;
        result.add(p);

        for (int j = i + p; j < size; j += p)
            bs.set(j);

        i = bs.nextClearBit(i + 1);
    }

    return result;
}
于 2015-03-01T12:16:32.630 回答
0

重构你的代码。丢弃临时数组,而是编写仅对整数进行素数测试的函数。它会相当快,因为​​您只使用本机类型。然后,例如,您可以循环并构建一个素数整数列表,然后最终将其转换为要返回的数组。

于 2009-02-25T15:00:20.240 回答
0

不确定这是否适合您的情况,但您可以看看我的方法。我用的是埃拉托色尼筛

  public static List<Integer> sieves(int n) {
        Map<Integer,Boolean> numbers = new LinkedHashMap<>();

        List<Integer> primes = new ArrayList<>();

        //First generate a list of integers from 2 to 30
        for(int i=2; i<n;i++){
            numbers.put(i,true);
        }

        for(int i : numbers.keySet()){
            /**
             * The first number in the list is 2; cross out every 2nd number in the list after 2 by 
             * counting up from 2 in increments of 2 (these will be all the multiples of 2 in the list):
             * 
             * The next number in the list after 2 is 3; cross out every 3rd number in the list after 3 by 
             * counting up from 3 in increments of 3 (these will be all the multiples of 3 in the list):
             * The next number not yet crossed out in the list after 5 is 7; the next step would be to cross out every
             * 7th number in the list after 7, but they are all already crossed out at this point,
             * as these numbers (14, 21, 28) are also multiples of smaller primes because 7 × 7 is greater than 30. 
             * The numbers not crossed out at this point in the list are all the prime numbers below 30:
             */
            if(numbers.get(i)){
                for(int j = i+i; j<n; j+=i) {
                    numbers.put(j,false);
                }
            }
        }


        for(int i : numbers.keySet()){
            for(int j = i+i; j<n && numbers.get(i); j+=i) {
                numbers.put(j,false);
            }
        }

        for(int i : numbers.keySet()){
           if(numbers.get(i)) {
               primes.add(i);
           }
        }
        return primes;
    }

为维基百科中说明的每个步骤添加了注释

于 2017-08-15T14:40:54.967 回答
0

我已经使用 HashMap 并发现它非常简单

import java.util.HashMap;
import java.util.Map;

/*Using Algorithms such as sieve of Eratosthanas */

public class PrimeNumber {

    public static void main(String[] args) {

        int prime = 15;
        HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();

        hashMap.put(0, 0);
        hashMap.put(1, 0);
        for (int i = 2; i <= prime; i++) {

            hashMap.put(i, 1);// Assuming all numbers are prime
        }

        printPrimeNumberEratoshanas(hashMap, prime);

    }

    private static void printPrimeNumberEratoshanas(HashMap<Integer, Integer> hashMap, int prime) {

        System.out.println("Printing prime numbers upto" + prime + ".....");
        for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) {
            if (entry.getValue().equals(1)) {
                System.out.println(entry.getKey());
                for (int j = entry.getKey(); j < prime; j++) {
                    for (int k = j; k * j <= prime; k++) {
                        hashMap.put(j * k, 0);
                    }
                }

            }
        }

    }

}

觉得这个很有效

于 2018-02-24T10:14:41.387 回答
0
public static void primes(int n) {
        boolean[] lista = new boolean[n+1];
        for (int i=2;i<lista.length;i++) {
            if (lista[i]==false) {
                System.out.print(i + " ");
            }
            for (int j=i+i;j<lista.length;j+=i) {
                lista[j]=true;
            }
        }
    }
于 2019-04-22T21:01:43.897 回答