Найдите положение простого числа

Мне нужно сделать обратное поиску N-го простого числа, т.е. учитывая простое число, мне нужно найти его позицию в

2, 3, 5, 7...

Простое число может быть большим, порядка 10^7. Кроме того, их очень много.

У меня есть индекс предварительно рассчитанных простых чисел, которые можно искать в двоичном коде, но у меня также есть ограничение по пространству в 50 КБ! Можно ли сделать сито? Или любой другой быстрый способ?

РЕДАКТИРОВАТЬ: Большое спасибо за все блестящие ответы, я их не ожидал! Я надеюсь, что они будут полезны другим, ищущим то же самое.


person max    schedule 02.01.2013    source источник
comment
Прежде всего, вы догадались, сколько их в возможном наборе ответов? Есть несколько способов решить эту проблему. Компромисс между пространством и скоростью, сжатие и математические уловки (например, двойные простые числа).   -  person Clinton Pierce    schedule 02.01.2013
comment
Когда я проверил еще раз, оказалось, что 664570+ простых чисел меньше 10 ^ 7. Двоичный поиск по предварительно вычисленной таблице простых чисел здесь не подходит.   -  person nhahtdh    schedule 02.01.2013
comment
Ого, отличная задача (+1), но если у вас нет чего-то радикально лучшего, это будет медленно.   -  person    schedule 02.01.2013
comment
См. Мои ответы на странице stackoverflow.com/questions/ 3918968 /, чтобы узнать, как разместить простой стол и быстро просеять. Вы должны получить ‹s› одно ‹/s› два простых числа на байт (см. en.wikipedia. org / wiki / Prime_gap). Двоичный поиск не нужен, вы можете оценить, с чего начать поиск в таблице, на основе P (n) ≈ n log n и оттуда линейный поиск.   -  person Potatoswatter    schedule 02.01.2013
comment
Я думаю, что просеивание здесь не очень практично ... вы можете взглянуть на метод подсчета простых чисел Дэррика Генри Лемера: en.wikipedia.org/wiki/Prime-counting_function он смог сделать 10 ^ 10 на IBM 701 еще в '59   -  person hexist    schedule 02.01.2013
comment
@Potatoswatter Ах, старая добрая теорема о простых числах! Я люблю эти бриллианты математики.   -  person    schedule 02.01.2013
comment
Наиболее эффективная логическая таблица (из просеивания) займет не менее 250000 байт (1 бит на число, 0,2 оптимистического коэффициента уменьшения из колесной факторизации).   -  person nhahtdh    schedule 02.01.2013
comment
Woops, редактирование вышеупомянутого комментария было неправильным, только одно простое число на байт. :П   -  person Potatoswatter    schedule 02.01.2013
comment
@Potatoswatter: 1 простое число на байт займет 660 КБ, поскольку существует 664570+ простых чисел ниже 1e7 (при условии хранения всей таблицы). Очень простая колесная факторизация (6) уже может сократить использование пространства до ~ 400 КБ. (Это не оптимально для перебора простых чисел, но хорошо для проверки того, является ли число простым).   -  person nhahtdh    schedule 02.01.2013
comment
@nhahtdh Да, но факторизация колеса не помогает выполнить обратный индекс в таблице. (Моя стратегия была разработана для хранения всего до 2 ^ 32, где битовый набор менее эффективен.) В любом случае, лучшие решения уже есть в ответах. Дополнительную информацию можно найти на странице математики.   -  person Potatoswatter    schedule 02.01.2013
comment
Что вы имеете в виду «у вас есть индекс предварительно вычисленных простых чисел» - на бумаге, на диске или в памяти?   -  person Colonel Panic    schedule 03.01.2013
comment
Используйте способ Мейселя / Лемера. O (sqrt (N)), поэтому с ограничением не более 10 ^ 7 все в порядке. Сублинейная временная сложность, так что это довольно быстро.   -  person Daniel Fischer    schedule 03.01.2013
comment
Всем большое спасибо. @ColonelPanic Под 50k я имел в виду ограничение на исходный файл, это подзадача онлайн-судьи!   -  person max    schedule 03.01.2013
comment
Это забавная проблема. Разместите ссылку, если она у вас есть.   -  person Colonel Panic    schedule 03.01.2013


Ответы (7)


Ваш диапазон составляет всего десять миллионов, что мало для такого рода вещей. У меня есть два предложения:

1) Создайте таблицу pi (n) с удобными интервалами, затем используйте сегментированное решето Эратосфена для подсчета простых чисел между двумя записями таблицы, которые заключают желаемое значение в скобки. Размер интервала определяет как размер требуемой таблицы, так и скорость, с которой могут быть вычислены результаты.

2) Используйте функцию Лежандра phi (x, a) и формулу подсчета простых чисел Лемера, чтобы вычислить результат напрямую. Функция phi требует некоторого хранилища, я не уверен, сколько именно.

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

РЕДАКТИРОВАТЬ 1:

Поразмыслив, у меня есть третья альтернатива:

3) Используйте логарифмический интеграл, чтобы оценить pi (n). Он монотонно возрастает и всегда больше, чем pi (n) в течение необходимого вам интервала. Но различия небольшие, никогда не превышают 200. Таким образом, вы можете предварительно вычислить различия для всех значений меньше десяти миллионов, составить таблицу из 200 точек изменения, а затем, когда потребуется, вычислить логарифмический интеграл и найти поправочный коэффициент в Таблица. Или вы можете сделать что-то подобное с функцией R Римана.

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

РЕДАКТИРОВАТЬ 2:

Как показывают комментарии, это не сработало. Пожалуйста, проигнорируйте мое третье предложение.

В качестве наказания за мою ошибку в предложении решения, которое не работает, я написал программу, которая использует таблицу значений пи (n) и сегментированное решето Эратосфена для вычисления значений пи (n) для n ‹10000000. I Я буду использовать Python, а не C, запрошенный исходным плакатом, потому что Python проще и легче читать в педагогических целях.

Начнем с вычисления простых чисел просеивания, меньших квадратного корня из десяти миллионов; эти простые числа будут использоваться как при построении таблицы значений pi (n), так и при выполнении сита, которое вычисляет окончательный ответ. Корень квадратный из десяти миллионов равен 3162,3. Мы не хотим использовать 2 в качестве простого числа просеивания - мы будем просеивать только нечетные числа и рассматривать 2 как особый случай - но мы хотим, чтобы следующее простое число было больше квадратного корня, так что список просеивание простых чисел никогда не исчерпывается (что могло бы вызвать ошибку). Итак, мы используем эту очень простую версию сита Эратосфена для вычисления простых чисел просеивания:

def primes(n):
    b, p, ps = [True] * (n+1), 2, []
    for p in xrange(2, n+1):
        if b[p]:
            ps.append(p)
            for i in xrange(p, n+1, p):
                b[i] = False
    return ps

Сито Эратосфена состоит из двух частей. Сначала составьте список чисел, меньших целевого числа, начиная с 2. Затем несколько раз просмотрите список, начиная с первого неперечеркнутого числа, и вычеркните все кратные числа из списка. Изначально 2 - это первое неперечеркнутое число, поэтому вычеркните 4, 6, 8, 10 и так далее. Тогда 3 будет следующим неперечеркнутым числом, поэтому вычеркните 6, 9, 12, 15 и так далее. Затем 4 было зачеркнуто как кратное 2, и следующее неперечеркнутое число - 5, поэтому вычеркните 10, 15, 20, 25 и так далее. Продолжайте, пока не будут учтены все неперечеркнутые числа; числа, которые не перечеркнуты, являются простыми. Цикл на p рассматривает каждое число по очереди, и если он не перечеркнут, цикл на i вычеркивает кратные.

Функция primes возвращает список из 447 простых чисел: 2, 3, 5, 7, 11, 13, ..., 3121, 3137, 3163. Мы вычеркиваем 2 из списка и сохраняем 446 простых чисел просеивания в глобальной переменной ps:

ps = primes(3163)[1:]

Основная функция, которая нам понадобится, считает простые числа в диапазоне. Он использует сито, которое мы будем хранить в глобальном массиве, чтобы его можно было повторно использовать вместо перераспределения при каждом вызове функции count:

sieve = [True] * 500

Функция count использует сегментированное решето Эратосфена для подсчета простых чисел в диапазоне от lo до hi (lo и hi оба включены в диапазон). Функция имеет четыре for цикла: первый очищает сито, последний подсчитывает простые числа, а два других выполняют просеивание аналогично простому сито, показанному выше:

def count(lo, hi):
    for i in xrange(500):
        sieve[i] = True
    for p in ps:
        if p*p > hi: break
        q = (lo + p + 1) / -2 % p
        if lo+q+q+1 < p*p: q += p
        for j in xrange(q, 500, p):
            sieve[j] = False
    k = 0
    for i in xrange((hi - lo) // 2):
        if sieve[i]: k += 1
    return k

Ядром функции является цикл for p in ps, который выполняет рассев, принимая по очереди каждое простое просеивание p. Цикл завершается, когда квадрат просеивающего простого числа больше, чем предел диапазона, поскольку все простые числа будут идентифицированы в этой точке (причина, по которой нам понадобилось следующее простое число больше квадратного корня, состоит в том, что было бы просеивающее простое число чтобы остановить цикл). Загадочная переменная q - это смещение в решето наименьшего кратного p в диапазоне от lo до hi (обратите внимание, что это не наименьшее кратное p в диапазоне, а индекс смещения наименьшего кратного p в диапазон, который может сбивать с толку). Оператор if увеличивает q, когда он обращается к числу, которое является полным квадратом. Затем петля на j снимает с сита точки, кратные p.

Мы используем функцию count двумя способами. Первое использование создает таблицу значений пи (п), кратных 1000; второе использование интерполирует в пределах таблицы. Сохраняем таблицу в глобальной переменной piTable:

piTable = [0] * 10000

Мы выбираем параметры 1000 и 10000 на основе исходного запроса, чтобы использовать память в пределах пятидесяти килобайт. (Да, я знаю, что исходный плакат смягчил это требование. Но мы все равно можем его соблюдать.) Десять тысяч 32-битных целых чисел займут 40 000 байт памяти, а для просеивания в диапазоне всего 1000 от lo до hi потребуется всего 500 байт. памяти и будет очень быстро. Вы можете попробовать другие параметры, чтобы увидеть, как они влияют на использование пространства и времени программой. Создание piTable осуществляется путем вызова count функции десять тысяч раз:

for i in xrange(1, 10000):
    piTable[i] = piTable[i-1] + \
        count(1000 * (i-1), 1000 * i)

Все вычисления до этого момента могут выполняться во время компиляции, а не во время выполнения. Когда я проводил эти вычисления на ideone.com, они занимали около пяти секунд, но это время не учитывается, потому что это можно сделать раз и навсегда, когда программист впервые напишет код. Как правило, вам следует искать возможности для перемещения кода из среды выполнения во время компиляции, чтобы ваши программы выполнялись очень быстро.

Осталось только написать функцию, которая фактически вычисляет количество простых чисел, меньших или равных n:

def pi(n):
    if type(n) != int and type(n) != long:
        raise TypeError('must be integer')
    if n < 2: return 0
    if n == 2: return 1
    i = n // 1000
    return piTable[i] + count(1000 * i, n+1)

Первый оператор if выполняет проверку типа. Второй оператор if возвращает правильный ответ на нелепый ввод. Третий оператор if обрабатывает 2 специально; наше рассевание делает 1 простым, а 2 - составным, оба из которых неверны, поэтому мы исправляем здесь. Затем i вычисляется как наибольший индекс piTable, меньший запрошенного n, и оператор return добавляет значение из piTable к количеству простых чисел между значением таблицы и запрошенным значением; предел hi равен n + 1, потому что в противном случае в случае, если n простое, оно не будет засчитано. Например, скажем:

print pi(6543223)

вызовет отображение числа 447519 на терминале.

Функция pi работает очень быстро. На ideone.com тысяча случайных вызовов pi (n) вычислялась примерно за полсекунды, то есть примерно половина по миллисекунде каждый; это включает время для генерации простого числа и суммирования результата, поэтому время на фактическое вычисление функции Пи составляет даже менее половины миллисекунды. Это довольно хорошая окупаемость наших инвестиций в создание стола.

Если вас интересует программирование с простыми числами, я довольно много поработал в своем блоге. Пожалуйста, приходите и заходите.

person user448810    schedule 02.01.2013
comment
Повторно «составьте таблицу из 200 точек изменения» - даже если ошибка небольшая, она не монотонна, поэтому может измениться много больше чем 200 раз для n вплоть до 10 ^ 7. Вы подсчитали количество изменений? - person James Waldby - jwpat7; 03.01.2013
comment
Нет, не видел. Спасибо за исправление. Тем не менее, количество изменений невелико, поэтому для этого потребуется совсем немного места. Однако я по-прежнему считаю, что просеивание предпочтительнее. - person user448810; 03.01.2013
comment
Между 3 и 1000 значение этажа (Li (x)) - pi (x) меняет значение 293 раза, а floor (Li (x) + .5) - pi (x) меняет значение 260 раз. Это не сработает. - person tmyklebu; 03.01.2013
comment
Это намного больше, чем я думал. Я согласен, это не сработает. Тогда давайте остановимся на сегментированном сите. - person user448810; 03.01.2013
comment
Мне нравится №1, он напоминает алгоритм шага гигантского пасынка для решения другой задачи. Подобно этому алгоритму, я ожидаю, что вы сможете получить примерно sqrt (N) для хранения и работы. Однако есть одна загвоздка: как найти две записи таблицы, которые заключают значение в скобки? Я думаю, вы можете использовать теорему о простых числах, чтобы сделать предположение, а затем внести исправления (возможно, просто линейное зондирование, чтобы найти записи в скобках). - person President James K. Polk; 03.01.2013
comment
@GregS: Допустим, у вас есть 10 ^ 3 записей в таблице с интервалом 10 ^ 4. Чтобы найти количество простых чисел меньше 567890, просейте интервал от 560000 до 570000, подсчитайте простые числа от 560000 до 567890 и сложите значение числа пи (560000) из таблицы. - person user448810; 03.01.2013
comment
@GregS: вы можете сделать это простым делением, как упоминал user448810. (Посмотрите мой ответ на код.) - person tmyklebu; 03.01.2013

Если вы априори знаете, что ввод является простым, вы можете использовать приближение pi (n) ≈ n / log n с небольшой таблицей исправлений для простых чисел, где округления результата недостаточно для получения правильного значения. п. Я думаю, что это ваш лучший выбор в рамках ограничения размера, помимо медленного подхода грубой силы.

person R.. GitHub STOP HELPING ICE    schedule 02.01.2013
comment
Размер этой таблицы очень важен. Теорема о простых числах в основном работает с большими числами; это может не очень хорошо применяться здесь, но только попытка покажет. - person Potatoswatter; 02.01.2013
comment
Прошло много времени с тех пор, как я играл с числами теоремы о простых числах, но я думаю, что попробовать стоит. - person R.. GitHub STOP HELPING ICE; 02.01.2013
comment
Я думаю, что он ищет точное число, и есть известные хорошие методы для его вычисления (метод Деррика Генри Лемера мне показался лучше всего, у Лежандра также есть метод, который, похоже, может быть осуществим в этих ограничениях) - person hexist; 02.01.2013
comment
Очевидно, ему нужно точное число. Я хочу сказать, что вы можете использовать приближение, основанное на инвертировании оценки теоремы о простых числах и настройке полученной функции, а затем добавлении таблицы исправлений для входных данных, с которыми она ошибается. - person R.. GitHub STOP HELPING ICE; 02.01.2013
comment
Это не сработает. Распределение простых чисел слишком неприятно. Теорема о простых числах говорит вам, что pi (n) асимптотичен n / log (n). Это означает, что мультипликативная разница между ними стремится к нулю. Вам нужно что-то, что находится в пределах аддитивной константы pi (n), чтобы подход исправления имел надежду на работу. (И n / log (n) не входит в аддитивную константу pi (n).) - person tmyklebu; 02.01.2013
comment
Очевидно, что мой подход бесполезен для вычисления pi (n). Однако проблема OP заключается в вычислении pi ^ -1 (p), по-видимому, в предположении, что вход p является простым. Мне до сих пор неясно, можно ли использовать предложенный мной подход для решения этой проблемы. - person R.. GitHub STOP HELPING ICE; 03.01.2013
comment
Кстати, я считаю, что другие предложения, которые были сделаны после моего, являются гораздо лучшими идеями. - person R.. GitHub STOP HELPING ICE; 03.01.2013
comment
Проблема в том, что асимптотические оценки числа pi слишком далеки от числа pi, чтобы их можно было использовать для вычисления pi ^ (- 1). Если вы округлите Li (x) или x / log (x) каким-либо последовательным образом, он будет идти вверх в достаточном количестве пунктов, которые полностью ошибочны. Обратите внимание, что производные Li (x) и x / log (x) являются монотонно убывающими функциями. Это просто не может летать, когда у вас есть много маленьких промежутков (подумайте о двойных простых числах) и больших промежутков в последовательности простых чисел, разбросанных повсюду. Асимптотические приближения действительно не дают вам достаточно информации. - person tmyklebu; 03.01.2013

Я бы предположил, что здесь будет работать эвристическая гибридная модель. Сохраните каждое n-е простое число, а затем выполните линейный поиск с помощью проверки простоты. Чтобы ускорить процесс, вы можете использовать быстрый и простой тест на простоту (например, тест Ферма с a==2) и предварительно вычислить ложные срабатывания. Некоторая тонкая настройка, основанная на максимальном размере ввода и ограничениях вашего хранилища, должна быть легко проработана.

person user295691    schedule 02.01.2013
comment
Между двумя числами будет около 60 простых чисел, если это будет реализовано, как вы сказали. - person nhahtdh; 02.01.2013
comment
И около 800 непростых чисел, прошедших тест, меньше 10 ^ 8. Вычисление 2 ^ n (mod n) равно O (log n); на практическом уровне это означает проверку ~ 1000 чисел на простоту, чтобы получить счет. Этот подход больше подходит для однократного тестирования, чем для пакетного тестирования, но все же должен быть быстрым. - person user295691; 02.01.2013
comment
Это хорошая идея. Вы можете сделать это лучше и избежать ошибок и чисел Кармайкла, используя детерминированный вариант Миллера-Рабина; проверка свидетелей 2, 7 и 61 работает для любых входных данных меньше нескольких миллиардов. - person tmyklebu; 03.01.2013
comment
Это компромисс между пространством и временем; детерминированный тест Ферма настолько быстр (и прост в реализации), что его сложно превзойти, а стоимость хранения чисел 2064 очень мала. (Кроме того, оказывается, что 2064 - правильное число для меньше 10 ^ 8; 850 меньше 10 ^ 7. Если мы проверим 2 и 3, то оно упадет до 492). - person user295691; 03.01.2013
comment
Miller-Rabin работает так же быстро, и вам не нужно впоследствии выполнять поиск в резервной таблице или пробное деление. Нет причин использовать прямой тест Ферма, если вместо него можно использовать Миллера-Рабина. - person tmyklebu; 03.01.2013
comment
Моя ошибка - я предполагал, что дополнительная работа, которую проделал алгоритм Миллера-Рабина, в среднем сделает его медленнее, но некоторые эксперименты показывают, что возможности раннего завершения ускоряют работу более чем достаточно, чтобы компенсировать это. - person user295691; 03.01.2013

Вот код, который работает. Вам следует заменить тест на простоту, основанный на пробном делении, на детерминированный Миллера-Рабина тест, который работает для вашего входного диапазона. Просеивание для нахождения простых чисел в подходящем малом диапазоне сработает лучше, чем пробное деление, но это шаг в неправильном направлении.

#include <stdio.h>
#include <bitset>
using namespace std;

short smallprimes[549]; // about 1100 bytes
char in[19531]; // almost 20k

// Replace me with Miller-Rabin using 2, 7, and 61.
int isprime(int j) {
 if (j<3) return j==2;
 for (int i = 0; i < 549; i++) {
  int p = smallprimes[i];
  if (p*p > j) break;
  if (!(j%p)) return 0;
 }
 return 1;
}

void init() {
 bitset<4000> siv;
 for (int i = 2; i < 64; i++) if (!siv[i])
  for (int j = i+i; j < 4000; j+=i) siv[j] = 1;
 int k = 0;
 for (int i = 3; i < 4000; i+=2) if (!siv[i]) {
  smallprimes[k++] = i;
 }

 for (int a0 = 0; a0 < 10000000; a0 += 512) {
  in[a0/512] = !a0;
  for (int j = a0+1; j < a0+512; j+=2)
   in[a0/512] += isprime(j);
 }
}

int whichprime(int k) {
 if (k==2) return 1;
 int a = k/512;
 int ans = 1 + !a;
 for (int i = 0; i < a; i++) ans += in[i];
 for (int i = a*512+1; i<k; i+=2) ans += isprime(i);
 return ans;
}

int main() {
 int k;
 init();
 while (1 == scanf("%i", &k)) printf("%i\n", whichprime(k));
}
person tmyklebu    schedule 02.01.2013
comment
Мне это нравится - хранение таблицы количества простых чисел меньше x через регулярный интервал намного более эффективно, чем мой подход к хранению самих простых чисел, потому что поиск тривиален. - person user295691; 03.01.2013

Следующее звучит как то, что вы ищете. http://www.geekviewpoint.com/java/numbers/index_of_prime. Там вы найдете код и модульные тесты. Поскольку ваш список относительно невелик (т.е. 10^7), он должен его обработать.

Обычно вы находите все простые числа между 2 и n, а затем подсчитываете все простые числа меньше n, чтобы найти индекс. Кроме того, если n не является простым, функция возвращает -1.

person Konsol Labapen    schedule 02.01.2013

Я сделал именно это однажды. Написал код, который дает n, может быстро найти n-е простое число, вплоть до n = 203542528, так что примерно 2e8. Или он может пойти в обратном направлении для любого числа n и определить, сколько простых чисел меньше n.

Используется база данных. Я храню все простые числа до определенной точки (sqrt моего верхнего предела). В вашем случае это означает, что вы должны хранить все простые числа до sqrt (1e7). Всего их 446, и вы можете сохранить этот список в сжатом виде, поскольку максимальная разница до этого момента составляет всего 34. После этой точки сохраните каждое k-е простое число (для некоторого значения k). Тогда достаточно быстрого сита. для генерации всех простых чисел за короткий интервал.

Итак, в MATLAB, чтобы найти 1e7-е простое число:

nthprime(1e7)
ans =
   179424673

Или он может найти количество простых чисел меньше 1e7:

nthprime(1e7,1)
ans =
      664579

Дело в том, что такую ​​базу данных легко создавать и искать. Если ваша база данных может быть не более 50 КБ, проблем быть не должно.

person Community    schedule 03.01.2013

То, что вы предлагаете, лучше всего. Предварительно вычислите (или загрузите) список простых чисел меньше 10 ^ 7, затем бинарный поиск их.

Всего 664579 простых чисел меньше 10 ^ 7, поэтому список будет занимать ~ 2,7 МБ пространства. Бинарный поиск для решения каждого случая будет очень быстрым - всего ~ 20 операций.

person Colonel Panic    schedule 02.01.2013
comment
но он говорит, что у него ограничение по объему 50k - person im so confused; 02.01.2013
comment
OP пишет Под 50k я фактически имел в виду ограничение исходного файла - person Colonel Panic; 03.01.2013