Статья Автор: Лебедев Дмитрий Алексеевич

Проблема K. Количество простых чисел на отрезке

Проблема K. Количество простых чисел на отрезке.

Дан отрезок [A;B]. Надо определить сколько простых чисел принадлежит отрезку.
Входные умения: проверка на простоту с помощью "поиска минимального делителя числа" (программа min_del)
Задание:
написать функцию count_prime(A, B), которая возвращает число простых чисел на отрезке
Способ 1 - перебрать все числа отрезка, для каждого определить "простое оно или нет"


Можно убедиться, что программа работает для A,B таких, что B-A не очень велико.
Для альтернативного способа можно предложить использование метода "решето Эратосфена", но не объяснять как его реализовать эффективно.
Возьмет описание из интернета:

Для нахождения всех простых чисел не больше заданного числа n, следуя методу Эратосфена, нужно выполнить следующие шаги:

  1. Выписать подряд все целые числа от двух до n (2, 3, 4, ..., n).
  2. Пусть переменная p изначально равна двум — первому простому числу.
  3. Зачеркнуть в списке числа от 2p до n, считая шагами по p (это будут числа, кратные p: 2p, 3p, 4p, ...).
  4. Найти первое незачёркнутое число в списке, большее чем p, и присвоить значению переменной p это число.
  5. Повторять шаги 3 и 4, пока возможно.
Возможно ученик его реализует "дословно". Приведем примерное решение. 
Программа sieve0(A,B) получает границы полуинтервала [A, B) и 
возвращает список из B элементов, таких что: B[p] = 1 если p - простое и 0 -в противном случае   


Можно убедиться, что на отрезке [1,1000007] решето уже дает большой выигрыш во времени.
Теперь можно приступить к этапу "повышение эффективности программ".
Для этого ещё раз "понять":
  • что 4 мы тоже "пытаемся вычеркивать". Значит надо добавить проверку d (делителя) на простоту
    то есть добавить условие M[d] == 1
Внесем это изменение и проверим прирост "эффективности", сравнив решения sieve_0 и sieve_1 можно увидеть в таблице в конце тетради
(сравниваем только развертывание решета, без подсчета количества простых)


Получим явное повышение эффективности (примерно в 5 раз).
Добавим еще одно "логическое усовершенствование".  
Вычеркивание начиналось с  2d. Однако, несложно убедиться, что его можно начинать с d*d, 
и так как, d*d должно быть меньше N, то d можно перебирать только до корня из N
Внесем и эти изменения 


Получили новое повышение эффективности (примерно в 2 раза).
Перебор d идет с шагом 1, но можно сделать с шагом 2, так как у нас только одно простое четное число.
Попробуем обработать d = 2 на этапе инициализации списка M
Внесем и эти изменения 


Получили небольшое, но повышение эффективности (примерно на 10%).
Ещё можно заметить, что перебор идет только по нечетным d и вычеркивать можно только на нечётных позициях,
а значит шаг для вычеркивания можно сделать чётным, то есть равным 2d 
Внесем и эти изменения 


Теперь повышение эффективности более внушительное (решение sieve_2 улучшено почти в два раза)
Кажется, что улучшений больше нет, но это логических. 
Теперь "технические" тонкости или "фишки Python"
Применим следующий прием: "замене изменения элементов списка в цикле" на  "замену среза "
for i in range(d * d,  B, 2 * d) : M[i] = 0
заменим на M[d*d:B:2 * d ] = [0] * len(range(d * d, B,  2 * d))
Внесем эти изменения
 


Итак, 1 фокус получился - результат увеличился в два раза.
Может быть len(range(d*d, B, 2*d) считается медленно и надо придумать формулу?
Это несложно сделать, если понять/проверить, что
len(range(a, b, c) = -(-(b-a)//c)
Ради интереса, проверим это, создав новую версию программы
 


Замена вычисления len(range(...)) похоже  прироста эффективности не дает, а код усложняет. 
На этом пока остановимся и подведем итоги:
  1. Эффективным методом получения/подсчёта чисел на отрезке (большом) может быть может быть метод решета Эратосфена
  2. "Решето" можно изначально сформировать в "предсформированном" виде (здесь есть место для творчества) 
  3. Для "просеивания", вместо цикла, эффективнее использовать срезы, вычисляя размер среза используя len(range ...)
  4. Попытка использовать константы True/False на Python "выигрыша" не дает (см. решение ниже)
  5.  Программа развертывания "решета Эратосфена" занимает  7 простых строк кода, работает достаточно быстро,
    поэтому метод "решета" может быть использован при решении большого класса задач 
Ниже приведены результаты работы вариантов программы по развертки решета на различные отрезки
и финальный текст программы

  1_000_000(мс) 10_000_000(сек) 100_000_000 (сек)
sieve_0 720 15,350 -
sieve_1 190 3,0 -
sieve_2 93 1,53 20,75
sieve_3 78 1,3 16,3
sieve_4 48 0,78 9,5
sieve_5 16 0,25 3,0

Печать