Как генерировать простые числа, используя правило 6*k + - 1

мы знаем, что все простые числа выше 3 могут быть сгенерированы с помощью:

6 * k + 1
6 * k - 1

однако мы все числа, генерируемые из приведенных выше формул, не являются простыми.

For Example:    
6 * 6 - 1 = 35 which is clearly divisible by 5.

чтобы устранить такие условия, я использовал метод сита и удаление чисел, которые являются факторами чисел, генерируемых из приведенной выше формулы.

используя факты:

число называется простым, если оно не имеет простых делителей.

  1. As мы можем генерировать все простые числа, используя приведенные выше формулы.
  2. если мы можем удалить все кратные вышеуказанных чисел, мы остаемся только с простыми числами.

для генерации простых чисел ниже 1000.

ArrayList<Integer> primes = new ArrayList<>();
primes.add(2);//explicitly add
primes.add(3);//2 and 3
int n = 1000;
for (int i = 1; i <= (n / 6) ; i++) {
//get all the numbers which can be generated by the formula
    int prod6k = 6 * i;
    primes.add(prod6k - 1);
    primes.add(prod6k + 1);
}
for (int i = 0; i < primes.size(); i++) {
    int k = primes.get(i);
    //remove all the factors of the numbers generated by the formula
    for(int j = k * k; j <= n; j += k)//changed to k * k from 2 * k, Thanks to DTing
    {           
        int index = primes.indexOf(j); 
        if(index != -1)
            primes.remove(index);
    }
}
System.out.println(primes);

однако этот метод правильно генерирует простые числа. Это работает намного быстрее, так как нам не нужно проверять все числа, которые мы проверяем в сите.

мой вопрос в том, что я пропустил какой-либо крайний случай? Этот было бы намного лучше, но я никогда не видел, чтобы кто-то использовал это. Я делаю что-то не так?

может ли этот подход быть намного более оптимизирован?


взяв boolean[] вместо ArrayList гораздо быстрее.

int n = 100000000;
boolean[] primes = new boolean[n + 1];
for (int i = 0; i <= n; i++)
    primes[i] = false;
primes[2] = primes[3] = true;
for (int i = 1; i <= n / 6; i++) {
    int prod6k = 6 * i;
    primes[prod6k + 1] = true;
    primes[prod6k - 1] = true;
}
for (int i = 0; i <= n; i++) {
    if (primes[i]) {
        int k = i;
        for (int j = k * k; j <= n && j > 0; j += k) {
               primes[j] = false;
        }
      }
}
for (int i = 0; i <= n; i++)
    if (primes[i]) 
        System.out.print(i + " ");

7 ответов


вам не нужно добавлять всех возможных кандидатов в массив. Вы можете создать набор для хранения всех не простых чисел.

также вы можете начать проверку на k * k, а не 2 * k

  public void primesTo1000() {
    Set<Integer> notPrimes = new HashSet<>();
    ArrayList<Integer> primes = new ArrayList<>();
    primes.add(2);//explicitly add
    primes.add(3);//2 and 3

    for (int i = 1; i < (1000 / 6); i++) {
      handlePossiblePrime(6 * i - 1, primes, notPrimes);
      handlePossiblePrime(6 * i + 1, primes, notPrimes);
    }
    System.out.println(primes);
  }

  public void handlePossiblePrime(
      int k, List<Integer> primes, Set<Integer> notPrimes) {
    if (!notPrimes.contains(k)) {
      primes.add(k);
      for (int j = k * k; j <= 1000; j += k) {
        notPrimes.add(j);
      }
    }
  }

непроверенный код, проверьте углы


здесь версия упаковки бита сетки как предложено в ответ ссылка @Will Ness. Вместо того, чтобы вернуть n th prime, эта версия возвращает список простых чисел до n:

public List<Integer> primesTo(int n) {
  List<Integer> primes = new ArrayList<>();
  if (n > 1) {
    int limit = (n - 3) >> 1;
    int[] sieve = new int[(limit >> 5) + 1];
    for (int i = 0; i <= (int) (Math.sqrt(n) - 3) >> 1; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0) {
        int p = i + i + 3;
        for (int j = (p * p - 3) >> 1; j <= limit; j += p)
          sieve[j >> 5] |= 1 << (j & 31);
      }
    primes.add(2);
    for (int i = 0; i <= limit; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0)
        primes.add(i + i + 3);
  }
  return primes;
}

кажется, в вашем обновленном коде есть ошибка, которая использует логический массив (он не возвращает все простые числа).

public static List<Integer> booleanSieve(int n) {
  boolean[] primes = new boolean[n + 5];
  for (int i = 0; i <= n; i++)
    primes[i] = false;
  primes[2] = primes[3] = true;
  for (int i = 1; i <= n / 6; i++) {
    int prod6k = 6 * i;
    primes[prod6k + 1] = true;
    primes[prod6k - 1] = true;
  }
  for (int i = 0; i <= n; i++) {
    if (primes[i]) {
      int k = i;
      for (int j = k * k; j <= n && j > 0; j += k) {
        primes[j] = false;
      }
    }
  }

  List<Integer> primesList = new ArrayList<>();
  for (int i = 0; i <= n; i++)
    if (primes[i])
      primesList.add(i);

  return primesList;
}

public static List<Integer> bitPacking(int n) {
  List<Integer> primes = new ArrayList<>();
  if (n > 1) {
    int limit = (n - 3) >> 1;
    int[] sieve = new int[(limit >> 5) + 1];
    for (int i = 0; i <= (int) (Math.sqrt(n) - 3) >> 1; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0) {
        int p = i + i + 3;
        for (int j = (p * p - 3) >> 1; j <= limit; j += p)
          sieve[j >> 5] |= 1 << (j & 31);
      }
    primes.add(2);
    for (int i = 0; i <= limit; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0)
        primes.add(i + i + 3);
  }
  return primes;
}

public static void main(String... args) {
  Executor executor = Executors.newSingleThreadExecutor();
  executor.execute(() -> {
    for (int i = 0; i < 10; i++) {
      int n = (int) Math.pow(10, i);
      Stopwatch timer = Stopwatch.createUnstarted();
      timer.start();
      List<Integer> result = booleanSieve(n);
      timer.stop();
      System.out.println(result.size() + "\tBoolean: " + timer);
    }

    for (int i = 0; i < 10; i++) {
      int n = (int) Math.pow(10, i);
      Stopwatch timer = Stopwatch.createUnstarted();
      timer.start();
      List<Integer> result = bitPacking(n);
      timer.stop();
      System.out.println(result.size() + "\tBitPacking: " + timer);
    }
  });
}

0   Boolean: 38.51 μs
4   Boolean: 45.77 μs
25  Boolean: 31.56 μs
168 Boolean: 227.1 μs
1229    Boolean: 1.395 ms
9592    Boolean: 4.289 ms
78491   Boolean: 25.96 ms
664116  Boolean: 133.5 ms
5717622 Boolean: 3.216 s
46707218    Boolean: 32.18 s
0   BitPacking: 117.0 μs
4   BitPacking: 11.25 μs
25  BitPacking: 11.53 μs
168 BitPacking: 70.03 μs
1229    BitPacking: 471.8 μs
9592    BitPacking: 3.701 ms
78498   BitPacking: 9.651 ms
664579  BitPacking: 43.43 ms
5761455 BitPacking: 1.483 s
50847534    BitPacking: 17.71 s

5-это первое число, генерируемое вашими критериями. Давайте посмотрим на числа, сгенерированные до 25:

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25

теперь давайте посмотрим на эти же числа, когда мы используем алгоритм сита Эратосфена:

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25

после удаления 2:

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25

после удаления 3:

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25

это то же самое, что и первый набор! Заметьте, они оба включите 25,что не является простым. Если подумать, то это очевидный результат. Рассмотрим любую группу из 6 последовательных цифр:

6k-3, 6k-2, 6k - 1, 6k, 6k + 1, 6k + 2

если мы немного учитываем, мы получаем:

3*(2k-1), 2*(3k - 1), 6k - 1, 6*(k), 6k + 1, 2*(3k + 1)

в любой группе из 6 последовательных чисел, три из них будут делиться на два, и два из них будут делиться на три. Именно эти цифры мы так далеки! Таким образом:

Ваш алгоритм использовать только 6k - 1 и 6k + 1 - Это точно так же, как в первых двух раундах решето Erathosthenes.

это довольно хорошее улучшение скорости по сравнению с ситом, потому что нам не нужно добавлять все эти дополнительные элементы только для их удаления. Это объясняет, почему ваш алгоритм работает и почему он не пропускает никаких дел, потому что это точно так же, как Сито.


в любом случае, я согласен, что как только вы создали простые числа, ваш boolean путь на сегодняшний день самый быстрый. я установил бенчмарк, используя ваш ArrayList Кстати, Ваш boolean[] кстати, и мой собственный путь, используя LinkedList и iterator.remove() (потому что удаления быстро в LinkedList. Вот код для моего теста. Обратите внимание, что я запускаю тест 12 раз, чтобы убедиться, что JVM разогрет, и я печатаю размер списка и изменяю размер n попытаться предотвратить слишком много!--185-->предсказания ветвлений оптимизация. Вы также можете получить быстрее во всех трех методах, используя += 6 в начальном семени вместо prod6k:

import java.util.*;

public class PrimeGenerator {
  public static List<Integer> generatePrimesArrayList(int n) {
    List<Integer> primes = new ArrayList<>(getApproximateSize(n));
    primes.add(2);// explicitly add
    primes.add(3);// 2 and 3

    for (int i = 6; i <= n; i+=6) {
      // get all the numbers which can be generated by the formula
      primes.add(i - 1);
      primes.add(i + 1);
    }

    for (int i = 0; i < primes.size(); i++) {
      int k = primes.get(i);
      // remove all the factors of the numbers generated by the formula
      for (int j = k * k; j <= n; j += k)// changed to k * k from 2 * k, Thanks
                                         // to DTing
      {
        int index = primes.indexOf(j);
        if (index != -1)
          primes.remove(index);
      }
    }
    return primes;
  }

  public static List<Integer> generatePrimesBoolean(int n) {
    boolean[] primes = new boolean[n + 5];
    for (int i = 0; i <= n; i++)
      primes[i] = false;
    primes[2] = primes[3] = true;

    for (int i = 6; i <= n; i+=6) {
      primes[i + 1] = true;
      primes[i - 1] = true;
    }

    for (int i = 0; i <= n; i++) {
      if (primes[i]) {
        int k = i;
        for (int j = k * k; j <= n && j > 0; j += k) {
          primes[j] = false;
        }
      }
    }

    int approximateSize = getApproximateSize(n);
    List<Integer> primesList = new ArrayList<>(approximateSize);
    for (int i = 0; i <= n; i++)
      if (primes[i])
        primesList.add(i);

    return primesList;
  }

  private static int getApproximateSize(int n) {
    // Prime Number Theorem. Round up
    int approximateSize = (int) Math.ceil(((double) n) / (Math.log(n)));
    return approximateSize;
  }

  public static List<Integer> generatePrimesLinkedList(int n) {
    List<Integer> primes = new LinkedList<>();
    primes.add(2);// explicitly add
    primes.add(3);// 2 and 3

    for (int i = 6; i <= n; i+=6) {
      // get all the numbers which can be generated by the formula
      primes.add(i - 1);
      primes.add(i + 1);
    }

    for (int i = 0; i < primes.size(); i++) {
      int k = primes.get(i);
      for (Iterator<Integer> iterator = primes.iterator(); iterator.hasNext();) {
        int primeCandidate = iterator.next();
        if (primeCandidate == k)
          continue; // Always skip yourself
        if (primeCandidate == (primeCandidate / k) * k)
          iterator.remove();
      }
    }
    return primes;
  }

  public static void main(String... args) {
    int initial = 4000;

    for (int i = 0; i < 12; i++) {
      int n = initial * i;
      long start = System.currentTimeMillis();
      List<Integer> result = generatePrimesArrayList(n);
      long seconds = System.currentTimeMillis() - start;
      System.out.println(result.size() + "\tArrayList Seconds: " + seconds);

      start = System.currentTimeMillis();
      result = generatePrimesBoolean(n);
      seconds = System.currentTimeMillis() - start;
      System.out.println(result.size() + "\tBoolean Seconds: " + seconds);

      start = System.currentTimeMillis();
      result = generatePrimesLinkedList(n);
      seconds = System.currentTimeMillis() - start;
      System.out.println(result.size() + "\tLinkedList Seconds: " + seconds);
    }
  }
}

и результаты последних исследований:

3432    ArrayList Seconds: 430
3432    Boolean Seconds: 0
3432    LinkedList Seconds: 90
3825    ArrayList Seconds: 538
3824    Boolean Seconds: 0
3824    LinkedList Seconds: 81
4203    ArrayList Seconds: 681
4203    Boolean Seconds: 0
4203    LinkedList Seconds: 100
4579    ArrayList Seconds: 840
4579    Boolean Seconds: 0
4579    LinkedList Seconds: 111

есть несколько вещей, которые можно оптимизировать.

для начала операции" contains "и" removeAll " для ArrayList являются довольно дорогими операциями (линейными для первого, в худшем случае квадратичными для последнего), поэтому вы можете не использовать ArrayList для этого. Хэш-или TreeSet имеет лучшие сложности для этого, будучи почти постоянным (сложности хэширования странные) и логарифмическим, я думаю

вы можете заглянуть в сито сита Эратосфена если вы хотите более эффективный альтометр сита,но это будет помимо вашего вопроса о трюке 6k +-1. Это немного, но не заметно дороже памяти, чем ваше решение, но намного быстрее.


может ли этот подход быть намного более оптимизирован?

ответ-да.

Я начну с того, что это is хорошая идея использовать сито на подмножестве числа в определенном диапазоне, и Ваше предложение делает именно это.

читать про генерация простых чисел:

...Кроме того, на основе ситовых формализмов некоторые целочисленные последовательности (последовательность A240673 in OEIS) построены, которые они также могут использоваться для генерации простых чисел в определенных интервалах.

смысл этого абзаца в том, что ваш подход, начинающийся с сокращенного списка целых чисел, действительно был принят Академией, но их методы более эффективны (но также, естественно, более сложны).


вы можете генерировать пробные номера с колесом, добавляя 2 и 4 поочередно, что исключает умножение в 6 * k +/- 1.

public void primesTo1000() {
  Set<Integer> notPrimes = new HashSet<>();
  ArrayList<Integer> primes = new ArrayList<>();
  primes.add(2);  //explicitly add
  primes.add(3);  //2 and 3

  int step = 2;
  int num = 5  // 2 and 3 already handled.
  while (num < 1000) {     
    handlePossiblePrime(num, primes, notPrimes);
    num += step;      // Step to next number.
    step = 6 - step;  // Step by 2, 4 alternately.
  }
  System.out.println(primes);
}

вероятно, наиболее подходящей стандартной структурой данных для сита Эратосфена является BitSet. Вот мое решение:

static BitSet genPrimes(int n) {
    BitSet primes = new BitSet(n);
    primes.set(2); // add 2 explicitly
    primes.set(3); // add 3 explicitly
    for (int i = 6; i <= n ; i += 6) { // step by 6 instead of multiplication
        primes.set(i - 1);
        primes.set(i + 1);
    }
    int max = (int) Math.sqrt(n); // don't need to filter multiples of primes bigger than max

    // this for loop enumerates all set bits starting from 5 till the max
    // sieving 2 and 3 is meaningless: n*6+1 and n*6-1 are never divisible by 2 or 3
    for (int i = primes.nextSetBit(5); i >= 0 && i <= max; i = primes.nextSetBit(i+1)) {
        // The actual sieve algorithm like in your code
        for(int j = i * i; j <= n; j += i)
            primes.clear(j);
    }
    return primes;
}

использование:

BitSet primes = genPrimes(1000); // generate primes up to 1000
System.out.println(primes.cardinality()); // print number of primes
// print all primes like {2, 3, 5, ...}
System.out.println(primes);
// print all primes one per line
for(int prime = primes.nextSetBit(0); prime >= 0; prime = primes.nextSetBit(prime+1))
    System.out.println(prime);
// print all primes one per line using java 8:
primes.stream().forEach(System.out::println);

версия на основе boolean может работать быстрее для small n значения, но если вам нужен, например, миллион простых чисел,BitSet будет превосходить его в несколько раз и на самом деле работает правильно. Вот хромой бенчмарк:

public static void main(String... args) {
    long start = System.nanoTime();
    BitSet res = genPrimes(10000000);
    long diff = System.nanoTime() - start;
    System.out.println(res.cardinality() + "\tBitSet Seconds: " + diff / 1e9);

    start = System.nanoTime();
    List<Integer> result = generatePrimesBoolean(10000000); // from durron597 answer
    diff = System.nanoTime() - start;
    System.out.println(result.size() + "\tBoolean Seconds: " + diff / 1e9);
}

выход:

664579  BitSet Seconds: 0.065987717
664116  Boolean Seconds: 0.167620323

664579 является правильным номером простых чисел ниже 10000000.


этот метод ниже показывает, как найти простые nos с помощью логики 6k+/-1

Это было написано на python 3.6

def isPrime(n):
    if(n<=1):
        return 0
    elif(n<4):   #2 , 3 are prime
        return 1
    elif(n%2==0):  #already excluded no.2 ,so any no. div. by 2 cant be prime
        return 0
    elif(n<9):   #5, 7 are prime and 6,8 are excl. in the above step
        return 1
    elif(n%3==0):
        return 1

    f=5         #Till now we have checked the div. of n with 2,3 which means with 4,6,8 also now that is why f=5
    r=int(n**.5)    #rounding of root n, i.e: floor(sqrt(n))    r*r<=n
    while(f<=r):
        if(n%f==0): #checking if n has any primefactor lessthan sqrt(n), refer LINE 1
            return 0
        if(n%(f+2)==0): #remember her we are not incrementing f, see the 6k+1 rule to understand this while loop steps ,you will see that most values of f are prime
            return 0
        f=f+6

    return 1    

def prime_nos():
    counter=2  #we know 2,3 are prime
    print(2)
    print(3)   #we know 2,3 are prime
    i=1
    s=5  #sum  2+3
    t=0

    n=int(input("Enter the upper limit( should be > 3: "))

    n=(n-1)//6   #finding max. limit(n=6*i+1) upto which I(here n on left hand side) should run
    while(i<n):#2*(10**6)):
        if (isPrime(6*i-1)):   
            counter=counter+1
            print(6*i-1)  #prime no                                                

        if(isPrime(6*i+1)):    
           counter=counter+1
           print(6*i+1)  #prime no                                

        i+=1

prime_nos()  #fn. call