В комментариях к одному из прошлых постов о решете Эратосфена был упомянут этот короткий алгоритм из Википедии:
Алгоритм 1:
1: для i := 2, 3, 4, ..., до n:
2: если lp[i] = 0:
3: lp[i] := i
4: pr[] += {i}
5: для p из pr пока p ≤ lp[i] и p*i ≤ n:
6: lp[p*i] := p
Результат:
lp - минимальный простой делитель для кажого числа до n
pr - список всех простых до n.
Алгоритм простой, но не всем он показался очевидным. Главная же проблема в том, что на Википедии нет доказательства, а ссылка на первоисточник (pdf) содержит довольно сильно отличающийся от приведенного выше алгоритм.
В этом посте я попытаюсь, надеюсь, доступно доказать, что этот алгоритм не только работает, но и делает это за линейную сложность.
Определения
— множество простых чисел.
— минимальный простой делитель числа:
— остальные множители:
Обратите внимание, все определения выше даны для .
Некоторые очевидные свойства введенных выше функций, которые будут использоваться дальше:
Доказательство
Лемма 1:
Доказательство: Т.к. любой делитель также является делителем , а не превосходит любой делитель , то не превосходит и любой делитель , включая наименьший из них. Единственная проблема в этом утверждении, если не имеет простых делителей, но это возможно только в случае , который исключен в условии леммы.
Пусть
Т.к. (по определению ), если бы нам было дано множество , то мы смогли бы вычислить для всех составных чисел до n. Это делает, например, следующий алгоритм:
Алгоритм 2:
1: Для всех (l,r) из E:
2: lp[l*r] := l;
Заметим, что , поэтому алгоритм 2 выше работает за линейную сложность.
Далее я докажу, что алгоритм 1 из Википедии на самом деле просто перебирает все элементы этого множества, ведь его можно параметризовать и по-другому.
Пусть
Лемма 2:
Доказательство:
Пусть => .
По определению : , . По лемме 1, .
т.к. , то .
Поскольку , .
Так же, по определению , , следовательно, .
Все 4 условия выполнены, значит, => .
Пусть . Пусть .
По определению , . Значит, — простой делитеть .
Т.к , то Значит,
По определению, Также, очевидно,
Все условия для выполнены =>
Следовательно,
Теперь осталось перебрать правильные и из определения множества и применить алгоритм 2. Именно это и делает Алгоритм 1 (только вместо r используется переменная i). Но есть проблема! Что бы перебрать все элементы множества для вычисления нам надо знать ведь в определении есть условие .
К счастью, эта проблема обходится правильным порядком перебора. Надо перебирать во внешнем цикле, а — во внутреннем. Тогда уже будет вычислено. Этот факт доказывает следующая теорема.
Теорема 1:
Алгоритм 1 поддерживает следующий инвариант: После выполнения итерации внешнего цикла при i=k, все простые числа до k включительно будут выделены в список pr. Также будет подсчитано для всех в массиве lp.
Доказательство:
Докажем по индукции. Для k=2 инвариант проверяется вручную. Единственное простое число 2 будет добавлено в список pr, потому что массив lp[] изначально заполнен нулями. Также, единственное составное число у которого — это 4. Несложно убедиться, что внутренний цикл выполнится ровно один раз (при n>3) и правильно выполнит lp[4] := 2.
Теперь допустим, что инвариант выполняется для итерации i=k-1. Докажем, что он будет выполнятся и для итерации i=k.
Для этого достаточно проверить что число i, если оно простое, будет добавлено в список pr и что будет подсчитано для всех составных чисел т.ч. Именно эти числа из инварианта для k не покрыты уже инвариантом для k-1.
Если i простое, то lp[i] будет равно 0, ведь единственная операция записи в массив lp, которая теоретически могла бы подсчитать lp[i] (в строке 6), всегда пишет в составные индексы, ведь p*i (для i > 1) — всегда составное. Поэтому число i будет добавлено в список простых. Также, в строке 3 будет подсчитано lp[i].
Если же i составное, то на начало итерации lp[i] уже будет подсчитано по инварианту для i=k-1, ведь или следовательно i попадает под условие инварианта в предыдущей итерации. Поэтому составное число i не будет добавлено в список простых чисел.
Далее, имея корректное lp[i] и все простые числа до i включительно цикл в строках 5-6 переберет все элементы , у которых вторая часть равна i:
- потому что оно из списка pr
- по условию останова цикла
- по условию останова цикла
- следует из
Все нужные простые числа в списке pr есть, т.к. нужны только числа до . Поэтому будут подсчитаны значения lp[] для всех составных чисел , у которых . Это ровно все те числа, которых не хватало при переходе от инварианта для k-1 к инварианту для k.
Следовательно, инавриант выполняется для любых i = 2..n.
По инварианту из Теоремы 1 для i=n получается, что все простые числа до n и все lp[] будут подсчитаны алгоритмом 1.
Более того, поскольку в строках 5-6 перебираются различные элементы множества , то суммарно внутренний цикл выполнит не более операций. Операция проверки в цикле выполнится ровно раз ( раз вернет true и n-1 раз, для каждого i, вернет false). Все оставшиеся операции вложены в один цикл по i от 2 до n.
Отсюда следует, что сложность алгоритма 1 — O(n).
Автор: Илья Николаевский