Как генерировать простые числа, используя правило 6*k + - 1
мы знаем, что все простые числа выше 3 могут быть сгенерированы с помощью:
6 * k + 1
6 * k - 1
однако мы все числа, генерируемые из приведенных выше формул, не являются простыми.
For Example:
6 * 6 - 1 = 35 which is clearly divisible by 5.
чтобы устранить такие условия, я использовал метод сита и удаление чисел, которые являются факторами чисел, генерируемых из приведенной выше формулы.
используя факты:
число называется простым, если оно не имеет простых делителей.
- As мы можем генерировать все простые числа, используя приведенные выше формулы.
- если мы можем удалить все кратные вышеуказанных чисел, мы остаемся только с простыми числами.
для генерации простых чисел ниже 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