web-dev-qa-db-ja.com

エラトステネスのふるいで素数を見つける(元々:この配列を準備するより良い方法はありますか?)

注:以下のバージョン2では、エラトステネスのふるいを使用しています。私が最初に尋ねたことを助けたいくつかの答えがあります。私はエラトステネスのふるい法を選択して実装し、質問のタイトルとタグを適切に変更しました。助けてくれたみんなに感謝します!

前書き

指定された上限よりも小さい素数を含む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. 両方の配列を反復処理して要素を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 に感謝):

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

配列のすべての要素を可能なすべての因子と比較して素数を見つける方法は、明らかに非効率的です。一度に配列全体に対して Sieve of Eratosthenes を実行すると、大幅に改善できます。比較がはるかに少ないことに加えて、除算ではなく加算も使用します。除算はずっと遅いです。

13
Paul Tomblin

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

ArrayList<Integer>を作成し、最後にint[]に変換します。

周りにはさまざまなサードパーティのIntList(etc)クラスがありますが、reallyがいくつかの整数のボクシングのヒットを心配していない限り、私はそれを心配しません。

ただし、Arrays.copyOfを使用して新しい配列を作成することもできます。また、必要に応じてサイズを2倍にしてサイズを変更し、最後にトリミングすることもできます。これは基本的にArrayListの動作を模倣することになります。

8
Jon Skeet

エラトステネスのふるいを使用したアルゴ

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

画像ソース: ウィキメディア

7
Yatendra Goel

最も簡単な解決策は、配列の代わりに Collections Framework のメンバーを返すことです。

2
Hank Gay

Java 1.5?List<Integer>を返してArrayList<Integer>を使用しないのはなぜですか?int[]を返す必要がある場合は、変換することで実行できます。処理の最後にint[]にリストします。

2
Bogdan

私は本当に効率的な実装をしています:

  1. 偶数を保持しないため、メモリ使用量が半分になります。
  2. BitSetを使用し、数値ごとに1ビットのみを必要とします。
  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;
}
1
Rok Kralj

Paul Tomblinが指摘しているように、より優れたアルゴリズムがあります。

しかし、あなたが持っているものを維持し、結果ごとのオブジェクトが大きすぎると仮定する:

配列に追加するだけです。したがって、比較的小さなint []配列を使用します。それが完全に使用されたら、それをリストに追加し、置換を作成します。最後に、適切なサイズの配列にコピーします。

または、int []配列のサイズを推測します。小さすぎる場合は、現在の配列サイズよりも数分の1大きいサイズのint []に置き換えます。これによるパフォーマンスのオーバーヘッドは、サイズに比例したままになります。 (これは最近のstackoverflowポッドキャストで簡単に議論されました。)

これで基本的なふるいが整いました。内側のループはtemp[i]*temp[i] > primeまで続ける必要があることに注意してください。

ようやくプログラムが終了しました最適化されたふるいです

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

これがあなたの状況に適しているかどうかはわかりませんが、私のアプローチを見ることができます。 エラトステネスのふるい を使って使った。

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

ウィキペディアに示されている各ステップのコメントを追加

0
KyelJmD

コードを再構築します。一時配列を破棄し、代わりに整数の素数テストのみを行う関数を記述します。ネイティブタイプのみを使用しているため、かなり高速になります。次に、たとえば、プライムである整数のリストをループして作成してから、最終的にそれを配列に変換して返すことができます。

0
unwind

私は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);
                    }
                }

            }
        }

    }

}

これは効果的だと思います

0
reactdontact
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;
            }
        }
    }
0
user7188242