Реализации алгоритмов/Решето Аткина

Решето́ А́ткина — метод нахождения всех простых чисел, не превышающих заданное натуральное limit. В отличие от решета Эратосфена, последовательно исключающего числа, кратные уже найденным простым, данный алгоритм производит предварительное просеивание, а затем из найденных чисел исключает кратные квадратам простых, благодаря чему имеет теоретически лучший показатель асимптотической сложности .

Авторы: Артур Оливер Лонсдэйл Аткин, Дэниэл Юлиус Бернштайн (англ. Arthur Oliver Lonsdale Atkin, Daniel Julius Bernstein).

Алгоритм

править
  1. Числа 2, 3 и 5 рассматриваются, как заведомо простые.
  2. Создаётся «решето» — список, сопоставляющий каждому натуральному числу n из диапазона (5; limit] флаг «простое\составное». Изначально все флаги устанавливаются в положение «составное».
  3. Для очередного n из диапазона (5; limit] находится остаток от деления на 60:
    • 1, 13, 17, 29, 37, 41, 49, 53: уравнение — 4x² + y² = n; (1)
    • 7, 19, 31, 43: уравнение — 3x² + y² = n; (2)
    • 11, 23, 47, 59: уравнение — 3x² − y² = n (при x > y) (3)
  4. Значение остатка, не равное ни одному из вышеуказанных, свидетельствует о том, что число n — составное и таким образом исключается из дальнейшего рассмотрения. Иначе, если соответствующее уравнение имеет нечётное число решений (пар натуральных значений x и y), n помечается в «решете» как «простое».
  5. Процедура повторяется начиная с шага № 3, пока не будут перебраны все n ≤ limit.
  6. В заключение для каждого найденного простого n все значения, кратные его квадрату, помечаются в «решете» как «составные».

Реализации

править

В приведённых ниже реализациях предприняты следующие шаги, направленные на оптимизацию вычислений:

  • Флаги «простое\составное» хранятся упакованными в массив битов (vector<bool> для С++, BitArray для C#, BitSet для Java, BitVec для Rust и т. п.), возвращаемый функцией. Значение n-го бита true соответствует простому n, false — составному.
  • Умножение на степень двойки производится операцией побитового сдвига (<<). Например a << 2 вместо 4 * a.
  • Во избежание переполнения вычисления ведутся в длинном целом типе, в «решето» значения заносятся в обычном целом типе (unsigned long long / unsigned в C++, long / int в C# и Java, u64 / u32 в Rust).
  • Квадраты последовательных натуральных чисел вычисляются по формуле (x + 1)² = x² + 2x + 1, т. е. прибавлением последовательных нечётных чисел. Другими словами, если squares — последовательность квадратов натуральных чисел {0² = 0, 1² = 1, 2² = 4…}, а odds — последовательность нечётных чисел {1, 3, 5…}, то squarei+1 = squarei + oddsi (для любого i ≥ 0).
  • n делится с остатком на 12 (а не на 60). Полученные значения 1 и 5 соответствуют случаю (1), 7 — (2), 11 — (3), прочие — составному n.
// atkin_sieve.h
#include <vector>


namespace AtkinSieve {
    std::vector<bool> getPrimesUpTo (unsigned const limit);
}


// atkin_sieve.cpp
#include "atkin_sieve.h"


std::vector<bool> AtkinSieve::getPrimesUpTo (unsigned const limit) {
    // Предварительное просеивание
    std::vector<bool> sieve(limit);    
    for (unsigned long long x2 = 1ull, dx2 = 3ull; x2 < limit; x2 += dx2, dx2 += 2ull)
        for (unsigned long long y2 = 1ull, dy2 = 3ull, n; y2 < limit; y2 += dy2, dy2 += 2ull) {
            // n = 4x² + y²
            n = (x2 << 2ull) + y2;
            if (n <= limit && (n % 12ull == 1ull || n % 12ull == 5ull))
                sieve[n].flip();
            // n = 3x² + y²
            n -= x2;
            if (n <= limit && n % 12ull == 7ull)
                sieve[n].flip();
            // n = 3x² - y² (при x > y)
            if (x2 > y2) {
                n -= y2 << 1ull;
                if (n <= limit && n % 12ull == 11ull)
                    sieve[n].flip();
            }
        }
    // Все числа, кратные квадратам, помечаются как составные
    unsigned r = 5u;
    for (unsigned long long r2 = r * r, dr2 = (r << 1ull) + 1ull; r2 < limit; ++r, r2 += dr2, dr2 += 2ull)
        if (sieve[r])
            for (unsigned long long mr2 = r2; mr2 < limit; mr2 += r2)
                sieve[mr2] = false;
    // Числа 2 и 3 — заведомо простые
    if (limit > 2u)
        sieve[2u] = true;
    if (limit > 3u)
        sieve[3u] = true;
    return sieve;
}

Пример использования:

// atkin_sieve_test.cpp
#include <iostream>

#include "atkin_sieve.h"


int main () {
    unsigned limit;
    std::cin >> limit;
    std::vector<bool> primes = AtkinSieve::getPrimesUpTo(limit);
    // Вывод последовательности
    for (unsigned number = 2u; number < limit; ++number)
        if (primes[number])
            std::cout << number << ' ';
    std::cout << '\n';
    return 0;
}
// AtkinSieve.cs
using System;
using System.Collections;


public static class AtkinSieve
{
    public static BitArray GetPrimesUpTo (int limit)
    {
        var sieve = new BitArray(limit + 1);
        // Предварительное просеивание
        for (long x2 = 1L, dx2 = 3L; x2 < limit; x2 += dx2, dx2 += 2L)
            for (long y2 = 1L, dy2 = 3L, n; y2 < limit; y2 += dy2, dy2 += 2L)
            {
                // n = 4x² + y²
                n = (x2 << 2) + y2;
                if (n <= limit && (n % 12L == 1L || n % 12L == 5L))
                    sieve[(int)n] ^= true;
                // n = 3x² + y²
                n -= x2;
                if (n <= limit && n % 12L == 7L)
                    sieve[(int)n] ^= true;
                // n = 3x² - y² (при x > y)
                if (x2 > y2)
                {
                    n -= y2 << 1;
                    if (n <= limit && n % 12L == 11L)
                        sieve[(int)n] ^= true;
                }
            }
        // Все числа, кратные квадратам, помечаются как составные
        int r = 5;
        for (long r2 = r * r, dr2 = (r << 1) + 1L; r2 < limit; ++r, r2 += dr2, dr2 += 2L)
            if (sieve[r])
                for (long mr2 = r2; mr2 < limit; mr2 += r2)
                    sieve[(int)mr2] = false;
        // Числа 2 и 3 — заведомо простые
        if (limit > 2)
            sieve[2] = true;
        if (limit > 3)
            sieve[3] = true;
        return sieve;
    }
}

Пример использования:

// AtkinSieveTest.cs
using System;
using System.Collections;
using System.IO;


public static class AtkinSieveTest
{ 
    public static void Main ()
    {
        int limit = int.Parse(Console.ReadLine());
        var primes = AtkinSieve.GetPrimesUpTo(limit);
        // Вывод последовательности
        for (int number = 2; number < limit; ++number)
            if (primes[number])
                Console.Write("{0} ", number);
        Console.WriteLine();
    }
}
// AtkinSieve.java
import java.util.BitSet;


public class AtkinSieve {
    public static BitSet getPrimesUpTo (int limit) {
        BitSet sieve = new BitSet();
        // Предварительное просеивание
        for (long x2 = 1L, dx2 = 3L; x2 < limit; x2 += dx2, dx2 += 2L)
            for (long y2 = 1L, dy2 = 3L, n; y2 < limit; y2 += dy2, dy2 += 2L) {
                // n = 4x² + y²
                n = (x2 << 2L) + y2;
                if (n <= limit && (n % 12L == 1L || n % 12L == 5L))
                    sieve.flip((int)n);
                // n = 3x² + y²
                n -= x2;
                if (n <= limit && n % 12L == 7L)
                    sieve.flip((int)n);
                // n = 3x² - y² (при x > y)
                if (x2 > y2) {
                    n -= y2 << 1L;
                    if (n <= limit && n % 12L == 11L)
                        sieve.flip((int)n);
                }
            }
        // Все числа, кратные квадратам, помечаются как составные
        int r = 5;
        for (long r2 = r * r, dr2 = (r << 1L) + 1L; r2 < limit; ++r, r2 += dr2, dr2 += 2L)
            if (sieve.get(r))
                for (long mr2 = r2; mr2 < limit; mr2 += r2)
                    sieve.set((int)mr2, false);
        // Числа 2 и 3 — заведомо простые
        if (limit > 2)
            sieve.set(2, true);
        if (limit > 3)
            sieve.set(3, true);
        return sieve;
    }
}

Пример использования:

// AtkinSieveTest.java
import java.util.BitSet;
import java.util.Scanner;


public class AtkinSieveTest {
    public static void main (String[] args) {
        Scanner scanner = new Scanner(System.in);
        int limit = scanner.nextInt();
        BitSet primes = AtkinSieve.getPrimesUpTo(limit);
        // Вывод последовательности
        for (int number = 2; number < limit; ++number)
            if (primes.get(number))
                System.out.format("%d ", number);
        System.out.println();
    }
}

Поскольку BitVec не входит в стандартную библиотеку Rust, его следует подключить через crates.io, указав bit-vec = "0.4.3" в разделе [dependencies] файла Cargo.toml.

// atkin_sieve.rs
extern crate bit_vec;

use self::bit_vec::BitVec;


pub fn get_primes_up_to (limit: u32) -> BitVec {
    let mut sieve = BitVec::from_elem(limit as usize, false);
    {
        let mut x2 = 1u64;
        let mut dx2 = 3u64;
        while x2 < limit as u64 {
            let mut y2 = 1u64;
            let mut dy2 = 3u64;
            let mut n: u64;
            let mut item: bool;
            while y2 < limit as u64 {
                // Просеивание:
                // n = 4x² + y²
                n = (x2 << 2) + y2;
                if n <= limit as u64 && (n % 12 == 1 || n % 12 == 5) {
                    item = sieve[n as usize];
                    sieve.set(n as usize, !item);
                }
                // n = 3x² + y²
                n -= x2;
                if n <= limit as u64 && n % 12 == 7 {
                    item = sieve[n as usize];
                    sieve.set(n as usize, !item);
                }
                // n = 3x² - y² (при x > y)
                if x2 > y2 {
                    n -= y2 << 1;
                    if n <= limit as u64 && n % 12 == 11 {
                        item = sieve[n as usize];
                        sieve.set(n as usize, !item);
                    }
                }
                y2 += dy2;
                dy2 += 2;
            }
            x2 += dx2;
            dx2 += 2;
        }
    }
    // Все числа, кратные квадратам, помечаются как составные
    {
        let mut r = 5u32;
        let mut r2 = (r * r) as u64;
        let mut dr2 = (r << 1) as u64 + 1;
        while r2 < limit as u64 {
            if sieve[r as usize] {
                let mut mr2 = r2;
                while mr2 < limit as u64 {
                    sieve.set(mr2 as usize, false);
                    mr2 += r2;
                }
            }
            r += 1;
            r2 += dr2;
            dr2 += 2;
        }
    }
    // 2 и 3 — заведомо простые
    if limit > 2 {
        sieve.set(2, true);
    }
    if limit > 3 {
        sieve.set(3, true);
    }
    sieve
}

Пример использования:

// atkin_sieve_test.rs
mod atkin_sieve;


// Функция для ввода данных
fn read_var <Type: std::str::FromStr> (var: &mut Type) -> bool {
    let mut input_text = String::new();
    std::io::stdin()
        .read_line(&mut input_text)
        .expect("failed to read from stdin");
    match input_text.trim().parse::<Type>() {
        Ok(value) => { *var = value; true },
        Err(..) => { false }
    }
}

fn main () {
    let mut limit = 0u32;
    if read_var(&mut limit) {
        let primes = atkin_sieve::get_primes_up_to(limit);
        for i in 2usize..limit as usize {
            if primes[i] {
                print!("{} ", i);
            }
        }
    }
}

В Cargo.toml следует добавить:

[[bin]]
name = "atkin_sieve_test"
path = "src/atkin_sieve_test.rs"


package atkinsieve

import "math"

func AtkinSieve(limit uint) []bool {
	// Инициализация чиназеса
	sqr_lim := uint(math.Sqrt(float64(limit)))
	is_prime := make([]bool, limit+1)
	is_prime[2] = true
	is_prime[3] = true

	// Предположительно простые — это целые с нечётным числом
	// представлений в данных квадратных формах.
	// x2 и y2 — это квадраты i и j (оптимизация).
	var n uint
	x2 := uint(0)
	for i := uint(1); i <= sqr_lim; i++ {
		x2 += 2*i - 1
		y2 := uint(0)
		for j := uint(1); j <= sqr_lim; j++ {
			y2 += 2*j - 1
			n = 4*x2 + y2
			if (n <= limit) && (n%12 == 1 || n%12 == 5) {
				is_prime[n] = !is_prime[n]
			}
			// n = 3 * x2 + y2;
			n -= x2 // Оптимизация
			if (n <= limit) && (n%12 == 7) {
				is_prime[n] = !is_prime[n]
			}
			// n = 3 * x2 - y2;
			n -= 2 * y2 // Оптимизация
			if (i > j) && (n <= limit) && (n%12 == 11) {
				is_prime[n] = !is_prime[n]
			}

		}
	}
	// Отсеиваем кратные квадратам простых чисел в интервале [5, sqrt(limit)].
	// (основной этап не может их отсеять)
	for i := uint(5); i <= sqr_lim; i++ {
		if is_prime[i] {
			n = i * i
			for j := n; j <= limit; j += n {
				is_prime[j] = false
			}
		}
	}
	return is_prime
}

См. также

править

Решето Сундарама

Решето Эратосфена

Ссылки

править

Взятая за основу реализация алгоритма на C++