Как стать автором
Обновить

Комментарии 34

Тут всё зависит от сценария использования, величин n и m. При больших m можно пойти ещё дальше и вместо сортировки вставками использовать оптимизированную структуру, например, кучу.


P.S. А преждевременная оптимизация — зло. Ведь куда проще написать (C#):


var result = array.OrderBy(...).Take(...).ToArray();

чем придумывать велосипед, который ещё надо реализовывать и тестировать.


Это я сагрился на "умельцы" и "алгоритмическое варварство".

«использовать оптимизированную структуру, например, кучу» — о чем я и написал в самом конце.
«Ведь куда проще написать» — конечно. Но что бы вы делали без C#? Вызвать библиотечную функцию — это одно, уметь самому реализвать ее функциональность — другое.

Изобретали бы программные библиотеки — вот что бы делали. Алгоритмы- алгоритмами, но писать свою реализацию должно быть крайним выходом.

«Изобретали бы программные библиотеки» — так это труднее, чем вызвать библ. функцию

Может и труднее, но это нужно сделать один раз.


Перед тем, как называть работу учеников "варварством" предложили бы им проанализировать оба подхода как с алгоритмической точки зрения, так и с точки зрения затрат на реализацию (включая поиск ошибок).

Что я и сделал.

эта задача решается классическим алгоритмом с линейной сложностью, реализованным например в C++ STL. Получение его из быстрой сортировки — отличная поучительная задача по теме. Вместо этого вы устраиваете какие-то "вычислительные эксперименты" с наивным алгоритмом квадратичной сложности.


Ну что тут можно сказать. Сочувствую вашим учащимся, представляю их шок, когда они столкнутся с задачами с какого-нибудь отбора к всеросу.

«эта задача решается классическим алгоритмом с линейной сложностью, реализованным например в C++ STL» — допустим. А без STL (с нуля) ее реализовать?

Если вы занимаетесь написанием высокопроизводительного кода на Python или даже Java, то вы явно делаете что-то не так.


Обратите внимание на время выполнения алгоритмов на приведённых вами графиках. Реализация на Python на 3 порядка медленнее реализации на Java. Ну и какой тогда в ней смысл?

Извините, но где там разница на три порядка (т.е. в тысячу раз)?
Ну вот же у вас в 300 раз различаются замеры
Заголовок спойлера

Вы не путаете? У Питона макс. время 120 миллисек, у Джавы — 450. Разница не три порядка…

Вот только у питона не 120, а 120000.
А по оптимизированной реализации ещё хуже: 60000 против 50. Оно и понятно: питон — скриптовый язык.

У Питона максимальное время (для 500тыс.чисел) — 120 миллисекунд.
«А по оптимизированной реализации ещё хуже: 60000 против 50» — какая это оптимизированная версия? Вы о чем?

Так у вас на графике для Python почему-то числа шестизначные. А ещё в коде для питона итерации идут до 500000, а в java — до 5000000.


Я прогнал тесты на своей системе. Получилось:
Python 3.9.1: 500000 0.07408 0.02520
Java (OpenJDK 14.0.2): 500000 0.02453 0.00598


Да, разница не на три порядка (видимо, запятая на графике — десятичная запятая, а не разделитель порядков). Но все равно: Java в несколько раз быстрее Python. А ваши графики говорят об обратном — так быть не может.

У меня JDK 8 и Питон 3.7. Да, запятую уберу — ошибка
да уж
Интересный момент, вполне возможно, что там имелось в виду (last-first) + (middle-first)log(middle-first), так как видимо предполагается, что элементы от [first, middle) должны быть отсортированы. В соседнем же nth_element уже сложность линейная (с стандартной execution policy).

Про сам алгоритм можно прочитать например здесь
В соседнем же nth_element уже сложность линейная (с стандартной execution policy).

Виноват, вообще не подумал об этой функции. Нам же действительно полная сортировка первых m элементов не нужна.

Классический – это какой? Сложить всё в кучу и выбрать m элементов из неё? O(n+mlog(n)), но требует памяти.
Или прогнать всё через кучу на m элементов? O(n
log(m)).


Upd: а, вы про поиск m-го элемента? Да, и в самом деле, его можно переделать.

В большинстве случаев задачу и стоит решать сортировкой. Все правильно ваши студенты делают. Да, есть небольшой перерасход ресурсов, но код выходит в большинстве случаев менее бажным. А если искать самое оптимальное решение, то стоит разобрать целую кучу граничных случаев, напрмер, когда надо найти число максимумов большее или сравнимое в с размером заданного массива. И потратить кучу времени на покрытие этого кода тестами.

Игры в ассимптотическую сложность далеко не всегда имеют смысл на практике. Более того любители ассимптотической сложности часто забывают, что ассимптотический анализ верен только для "достаточно больших значений". Ну, просто как простейший пример — hash map контейнер для 2-5 элементов в нем в подавляющем большинстве случаев окажется медленнее, чем тупейший линейный список. Вот и вся ассимптотика....

Что тут сказать — надо читать классику. Эта задача хорошо рассмотрена в Numerical Recipes. Во втором издании это параграф 8.5 Selecting the Mth largest. Наверно, у Кнута ещё лучше описано.

В частности, предлагается алгоритм, похожий на Quicksort, но существенно упрощенный, с асимптотической сложностью O(n), где n — размер массива.

Поиск m максимальных элементов — это ещё не самый трудный вариант данной задачи. Что если в массиве из 10000 элементов надо найти такой, который находится на 5000м месте по величине? Это, между прочим, задача поиска медианы, часто встречается на практике.

И если у разработчика нет специализированного алгоритма выбора элемента с заданным рангом — то быстрее всего приемлемое решение можно реализовать именно через сортировку.
Сложность такой операции O(n log m). Это вставка в отсортированный массив размером m. Классический случай, и я не понимаю в чём именно у автора сложности с ним.
А разве я писал про «сложности»? Я сравнивал сортировку и вставку в упорядоченный массив для поиска m максимумов. И показал, что даже простая вставка быстрее. Хотя теоретически это вполне очевидно. Но решил проверить — насколько быстрее.
Этот процесс имеет вычислительную сложность O(nm)

А разве я писал про «сложности»

Вы не видите разницы между O(nm) и O(n log m)?
Полная сортировка — n log n. Вставка в упорядоченный массив — log m. M максимумов — N log M
Ну очевидно, что при очень малых М — линейная вставка быстрее. И что? Это тема для статьи?
Вы сравнивали не для m максимумов, а для 10
Банальные изменения исходного алгоритма:

Вариант 1. Для хранения найденных значений используем не упорядоченный массив длины m, а пирамиду (heap), упорядоченную по возрастанию — получаем вычислительную сложность O(n * log(m))

Вариант 2. Преобразуем исходный массив в пирамиду O(n), упорядоченную по убыванию, и выбираем из пирамиды m максимальных значений O(m * log(n)) — получаем вычислительную сложность O(n + m * log(n))
Да, о чем я и писал в самом конце. Это банальные, но не слишком простые изменения
Плохо без деревьев в питоне жить :(
Я сравнивал этот простой алгоритм с «heap»-реализацией. Он выглядел вполне достойно
Вы использовали в сравнении классический вариант кучи, или более быстрый habr.com/ru/company/edison/blog/509330?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации