使用Eratosthenes的筛子找到素数(原来:有更好的方法来准备这个阵列吗?)

时间:2009-02-25 14:54:14

标签: java arrays primes sieve-of-eratosthenes

注意:下面的版本2使用了Eratosthenes的Sieve。有几个答案有助于我最初的问题。我选择了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. 我返回正确的数组 只保留素数的维度 我生成的数字。
  6. 问题

    1. 我可以复制整个块(一次) {38}有非零的temp[] 要素primes[] 无需迭代 两个数组并复制元素 一个接一个?
    2. 是否有任何数据结构 表现得像一个基元数组 可以随着元素的增加而增长, 而不是要求维度 在实例化?是什么 性能惩罚相比 使用基元数组?

    3. 第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)使用Sieve of 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;
      }
      

14 个答案:

答案 0 :(得分:13)

通过将数组的每个元素与每个可能的因子进行比较,找到素数的方法非常低效。您可以通过立即对整个阵列执行Sieve of Eratosthenes来极大地改进它。除了做更少的比较,它还使用加法而不是除法。分工比较慢。

答案 1 :(得分:9)

ArrayList<> Eratosthenes筛选

// 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;
}

答案 2 :(得分:8)

创建ArrayList<Integer>,然后在结尾处转换为int[]

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

您可以使用Arrays.copyOf创建新数组。您可能还希望每次需要时将尺寸加倍,然后在最后修剪。这基本上就是模仿ArrayList行为。

答案 3 :(得分:7)

Algo使用Eratosthenes筛选

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;
}

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

enter image description here

图片来源:WikiMedia

答案 4 :(得分:2)

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

答案 5 :(得分:2)

您使用的是Java 1.5吗?为什么不返回List<Integer>并使用ArrayList<Integer>?如果您确实需要返回int[],则可以在处理结束时将列表转换为int[]

答案 6 :(得分:1)

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

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

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

或者,猜猜int []数组的大小。如果它太小,请用大小比当前数组大小大一小部分的int []替换。其性能开销与大小成正比。 (在最近的stackoverflow播客中对此进行了简要讨论。)

答案 7 :(得分:1)

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

答案 8 :(得分:1)

我的实施非常有效:

  1. 我们不保留偶数,因此将内存使用量减半。
  2. 我们使用BitSet,每个号码只需要一位。
  3. 我们估计区间上素数的上限,因此我们可以适当地设置数组的initialCapacity
  4. 我们不会在循环中执行任何分割。
  5. 以下是代码:

    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;
    }
    

答案 9 :(得分:0)

重构您的代码。抛出临时数组,而是编写只是初始化测试整数的函数。它会相当快,因为​​你只使用本机类型。然后,您可以循环并构建一个整数列表,然后再将其转换为要返回的数组。

答案 10 :(得分:0)

我终于完成了这个程序 这是一个优化的筛子

public static int[] generatePrimes(int max) {
    boolean[] isComposite = new boolean[max + 1];
    LinkedList<Integer> Primes = new LinkedList<>(); //linkedlist so not have to iterate 2 times
    int sqrt = (int) Math.sqrt(max); //end at the square root
    for (int i = 2; i <= sqrt; i++) {
        if (!isComposite[i]) {
            int s = i*i; //start from the prime's square
            while (s <= max) {
                isComposite[s] = true; //oh no its a not prime
                s+=i;
            }
        }
    }
    for(int i = 2; i < max; i++){
        if(!isComposite[i]){
            Primes.add(i);
        }
    }
    int[] result = new int[Primes.size()];
    for (int i = 0; i < result.length; i++) {
        result[i] = Primes.get(i);
    }
    return result;
}

答案 11 :(得分:0)

不确定这是否适合您的情况,但您可以查看我的方法。我使用Sieve of Eratosthenes来使用我的。

  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;
    }

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

答案 12 :(得分: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);
                    }
                }

            }
        }

    }

}

认为这是有效的

答案 13 :(得分: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;
            }
        }
    }