Как сделать сортировку в Excel по возрастанию и по убыванию. Команда SORT - сортировка строк в текстовом файле

Сортировка данных в Excel это очень полезная функция, но пользоваться ней следует с осторожностью. Если большая таблица содержит сложные формулы и функции, то операцию сортировки лучше выполнять на копии этой таблицы.

Во-первых, в формулах и функциях может нарушиться адресность в ссылках и тогда результаты их вычислений будут ошибочны. Во-вторых, после многократных сортировок можно перетасовать данные таблицы так, что уже сложно будет вернуться к изначальному ее виду. В третьих, если таблица содержит объединенные ячейки, то следует их аккуратно разъединить, так как для сортировки такой формат является не приемлемым.

Сортировка данных в Excel

Какими средствами располагает Excel для сортировки данных? Чтобы дать полный ответ на этот вопрос рассмотрим его на конкретных примерах.

Подготовка таблицы для правильной и безопасной сортировки данных:


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

Чтобы отсортировать всю таблицу относительно одного столбца выполните следующее:



Данные отсортированные по всей таблице относительно столбца «Чистая прибыль».



Как в Excel сделать сортировку в столбце

Теперь отсортируем только один столбец без привязки к другим столбцам и целой таблицы:

Столбец отсортирован независимо от других столбцов таблицы.

Сортировка по цвету ячейки в Excel

При копировании таблицы на отдельный лист мы переносим только ее значения с помощью специальной вставки. Но возможности сортировки позволяют нам сортировать не только по значениям, а даже по цветам шрифта или цветам ячеек. Поэтому нам нужно еще переносить и форматы данных. Для этого:


Теперь копия таблицы содержит значения и форматы. Выполним сортировку по цветам:

  1. Выделите таблицу и выберите инструмент «Данные»-«Сортировка».
  2. В параметрах сортировки снова отмечаем галочкой «Мои данные содержат заголовки столбцов» и указываем: «Столбец» – Чистая прибыль; «Сортировка» – Цвет ячейки; «Порядок» – красный, сверху. И нажмите ОК.

Сверху у нас теперь наихудшие показатели по чистой прибыли, которые имеют наихудшие показатели.


Примечание. Дальше можно выделить в этой таблице диапазон A4:F12 и повторно выполнить второй пункт этого раздела, только указать розовый сверху. Таким образом в первую очередь пойдут ячейки с цветом, а после обычные.

Я пытаюсь понять различия между Сортировка вставки и Сортировка выбора.

Оба они, похоже, имеют два компонента: несортированный список и отсортированный список. Они оба, кажется, берут один элемент из несортированного списка и помещают его в отсортированный список в нужном месте. Я видел некоторые сайты/книги, говорящие, что сортировка сортировки делает это путем замены по одному, а сортировка вставки просто находит нужное место и вставляет его. Тем не менее, я видел, что в других статьях говорится что-то, говоря, что сортировка вставки также своп. Следовательно, я смущен. Есть ли какой-нибудь канонический источник?

15 ответов

Выбор Сортировка:

Учитывая список, возьмите текущий элемент и обменивайте его с наименьшим элементом в правой части текущего элемента.

Вставка Сортировка:

Учитывая список, возьмите текущий элемент и вставьте его в соответствующую позицию списка, настроив список каждый раз, когда вы вставляете. Это похоже на размещение карт в карточной игре.

Время Сложность сортировки выбора всегда n(n - 1)/2 , тогда как сортировка вставки имеет более сложную временную сложность, так как ее наихудшая сложность n(n - 1)/2 . Как правило, это будет меньше или равно сравнению, тогда n(n - 1)/2 .

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

Разница заключается в том, что делает внутренний цикл:

    В сортировке сортировки внутренний цикл находится над несортированными элементами. Каждый проход выбирает один элемент и перемещает его в свое конечное местоположение (в текущем конце отсортированной области).

    При сортировке вставки каждый проход внутреннего цикла повторяется по отсортированным элементам. Сортированные элементы смещаются до тех пор, пока петля не найдет нужное место для вставки следующего несортированного элемента.

Итак, в сортировке сортируются отсортированные элементы в порядке вывода и остаются на месте после их поиска. И наоборот, при сортировке вставки несортированные элементы остаются включенными до тех пор, пока не будут использованы в порядке ввода, тогда как элементы отсортированной области будут перемещаться вокруг.

Что касается обмена: выбор сортировки делает одну свопинг за проход внутреннего цикла. Сортировка вставки обычно сохраняет элемент, который должен быть вставлен как temp , перед внутренним циклом, оставляя место для внутреннего цикла смещать отсортированные элементы на единицу, а затем копирует temp в точку ввода после.

Возможно, путаница заключается в том, что вы сравниваете описание сортировки связанного списка с описанием сортировки массива. Но я не могу быть уверен, так как вы не указали свои источники.

Самый простой способ понять алгоритмы сортировки - это часто, чтобы получить подробное описание алгоритма (не такие неопределенные вещи, как "этот сорт использует swap. Где-то я не говорю, где"), получить игровые карты (5-10 должно быть достаточно для простых алгоритмов сортировки) и запустить алгоритм вручную.

Сортировка сортировки: сканирование через несортированные данные, которые ищут наименьший оставшийся элемент, затем поменяйте его на позицию сразу после отсортированных данных. Повторяйте до конца. При сортировке списка вам не нужно менять минимальный элемент на позицию, вместо этого вы можете удалить список node из его старой позиции и вставить его в новый.

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

Вставка сортировки может использовать swap во время фазы "сканирования", но не обязательно, и это не самый эффективный способ, если вы не сортируете массив типа данных, который: (a) не может быть перемещен, только скопирован или местами; и (b) дороже копирования, чем для обмена. Если сортировка вставки использует swap, то, как она работает, является то, что вы одновременно выполняете поиск места и помещаете туда новый элемент, многократно меняя новый элемент с элементом непосредственно перед ним, до тех пор, пока элемент до него больше, чем Это. Когда вы достигнете элемента, который не больше, вы нашли правильное местоположение и перейдете к следующему новому элементу.

Логика для обоих алгоритмов весьма схожа. Оба они имеют частично отсортированную подматрицу в начале массива. Единственное различие заключается в том, как они ищут следующий элемент, который будет помещен в отсортированный массив.

    Сортировка вставки : добавляет следующий элемент в правильное положение;

    Сортировка сортировки : выбирает наименьший элемент и обменивает его с текущим элементом;

Кроме того, Сортировка вставки стабильна, в отличие от Выбор Сортировка .

Я реализовал оба в python, и стоит отметить, насколько они похожи:

Def insertion(data): data_size = len(data) current = 1 while current < data_size: for i in range(current): if data < data[i]: temp = data[i] data[i] = data data = temp current += 1 return data

С небольшим изменением можно выполнить алгоритм выбора Сортировка.

Def selection(data): data_size = len(data) current = 0 while current < data_size: for i in range(current, data_size): if data[i] < data: temp = data[i] data[i] = data data = temp current += 1 return data

В двух словах, я думаю, что сортировка сортировки сначала ищет наименьшее значение в массиве, а затем выполняет обмен, тогда как сортировка вставки принимает значение и сравнивает его с каждым значением, оставшимся до него (за ним). Если значение меньше, оно меняет местами. Затем одно и то же значение сравнивается снова, и если оно меньше позади него, оно снова меняет местами. Надеюсь, это имеет смысл!

Выбор Сортировка: по мере того как вы начинаете создавать отсортированный подсписчик, алгоритм гарантирует, что отсортированный подсписчик всегда полностью сортируется не только по его собственным элементам, но также и по полному массиву, то есть как отсортированному, так и несортированному подсписку. Таким образом, новый наименьший элемент, однажды найденный из несортированного подсписок, будет просто добавлен в конец отсортированного подсписок.

Вставка Сортировка: алгоритм снова разделяет массив на две части, но здесь элемент выбирается из второй части и вставлен в правильное положение в первую часть. Это никогда не гарантирует, что первая часть будет сортироваться в терминах полного массива, хотя, конечно, в последнем проходе каждый элемент находится в правильной сортированной позиции.

Я дам еще одну попытку: рассмотрим, что происходит в счастливом случае почти отсортированного массива.

При сортировке массив можно представить как имеющее две части: левая сторона - отсортированная, правая сторона - несортированная.

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

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

ВЫБОР СОРТИРОВКИ
Предположим, что существует массив чисел, написанных определенным/случайным образом, и мы можем сказать, что мы должны упорядочиваться в порядке возрастания. Также возьмите по одному числу за раз и замените их наименьшим номером. доступных в списке. сделав этот шаг, мы в конечном итоге получим желаемый результат.

ВСТАВКА СОРТИРОВКА
Помните о том же предположении, но единственное отличие состоит в том, что на этот раз мы выбираем по одному числу за раз и вставляем его в предварительно отредактированную часть, что уменьшает сравнение и, следовательно, более эффективно.

Короче говоря,

Сортировка сортировки: Выберите первый элемент из несортированного массива и сравните его с оставшимися несортированными элементами. Он похож на сортировку Bubble, но вместо того, чтобы заменять каждый меньший элемент, сохраняет индекс наименьшего элемента обновленным и заменяет его в конце каждого цикла.

Сортировка вставки: . Она противоположна сортировке выбора, где он выбирает первый элемент из несортированного подматрица и сравнивает его с отсортированным подматрицей и вставляет самый маленький найденный элемент и сдвигает все отсортированные элементы от его права до первого несортированного элемента.

Внутренний цикл сортировки вставки проходит через уже отсортированные элементы (в отличие от сортировки сортировки). Это позволяет ему прервать внутренний цикл, когда найдено правильное положение . Который означает, что:

  1. Внутренний цикл будет проходить только половину его элементов в среднем случае.
  2. Внутренний цикл будет прерван раньше, если массив почти отсортирован.
  3. Внутренний цикл будет отменен немедленно, если массив уже отсортирован, что делает сложность сортировки вставки линейной в этом случае.

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

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

Думаю, всем известно, как выполнить сортировку по столбцу в алфавитном порядке или по возрастанию / убыванию. Это делается одним нажатием кнопки А-Я (A-Z) и Я-А (Z-A) в разделе Редактирование (Editing) на вкладке Главная (Home) либо в разделе Сортировка и фильтр (Sort & Filter) на вкладке Данные (Data):

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

Сортировка по нескольким столбцам

Я покажу Вам, как в Excel сортировать данные по двум или более столбцам. Работа инструмента показана на примере Excel 2010 – именно эта версия установлена на моём компьютере. Если Вы работаете в другой версии приложения, никаких затруднений возникнуть не должно, поскольку сортировка в Excel 2007 и Excel 2013 работает практически так же. Разницу можно заметить только в расцветке диалоговых окон и форме кнопок. Итак, приступим…


Сортировать данные по нескольким столбцам в Excel оказалось совсем не сложно, правда? Однако, в диалоговом окне Сортировка (Sort) кроется значительно больше возможностей. Далее в этой статье я покажу, как сортировать по строке, а не по столбцу, и как упорядочить данные на листе в алфавитном порядке по заголовкам столбцов. Вы также научитесь выполнять сортировку данных нестандартными способами, когда сортировка в алфавитном порядке или по значению чисел не применима.

Сортировка данных в Excel по заголовкам строк и столбцов

Я полагаю, что в 90% случаев сортировка данных в Excel выполняется по значению в одном или нескольких столбцах. Однако, иногда встречаются не такие простые наборы данных, которые нужно упорядочить по строке (горизонтально), то есть изменить порядок столбцов слева направо, основываясь на заголовках столбцов или на значениях в определённой строке.

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

Нам нужно отсортировать этот список фотокамер по наиболее важным для нас параметрам. Для примера первым делом выполним сортировку по названию модели:


В результате сортировки у Вас должно получиться что-то вроде этого:

В рассмотренном нами примере сортировка по заголовкам столбцов не имеет серьёзной практической ценности и сделана только для того, чтобы продемонстрировать Вам, как это работает. Таким же образом мы можем сделать сортировку нашего списка фотокамер по строке, в которой указаны размеры, разрешение, тип сенсора или по любому другому параметру, который сочтём более важным. Сделаем ещё одну сортировку, на этот раз по цене.

Наша задача – повторить описанные выше шаги 1 – 3. Затем на шаге 4 вместо строки 1 выбираем строку 4 , в которой указаны розничные цены (Retail Price). В результате сортировки таблица будет выглядеть вот так:

Обратите внимание, что отсортированы оказались данные не только в выбранной строке. Целые столбцы меняются местами, но данные не перемешиваются. Другими словами, на снимке экрана выше представлен список фотокамер, расставленный в порядке от самых дешёвых до самых дорогих.

Надеюсь, теперь стало ясно, как работает сортировка по строке в Excel. Но что если наши данные должны быть упорядочены не по алфавиту и не по возрастанию / убыванию?

Сортировка в произвольном порядке (по настраиваемому списку)

Если нужно упорядочить данные в каком-то особом порядке (не по алфавиту), то можно воспользоваться встроенными в Excel настраиваемыми списками или создать свой собственный. При помощи встроенных настраиваемых списков Вы можете сортировать, к примеру, дни недели или месяцы в году. Microsoft Excel предлагает два типа таких готовых списков – с сокращёнными и с полными названиями.

Предположим, у нас есть список еженедельных дел по дому, и мы хотим упорядочить их по дню недели или по важности.


Готово! Теперь домашние дела упорядочены по дням недели:

Замечание: Если Вы планируете вносить изменения в эти данные, помните о том, что добавленные новые или изменённые существующие данные не будут отсортированы автоматически. Чтобы повторить сортировку, нажмите кнопку Повторить (Reapply) в разделе Сортировка и фильтр (Sort & Filter) на вкладке Данные (Data).

Как видите, сортировка данных в Excel по настраиваемому списку – задача вовсе не сложная. Ещё один приём, которому мы должны научиться – сортировка данных по собственному настраиваемому списку.

Сортировка данных по собственному настраиваемому списку

В нашей таблице есть столбец Priority – в нём указаны приоритеты задач. Чтобы упорядочить с его помощью еженедельные задачи от более важных к менее важным, выполним следующие действия.

Повторите шаги 1 и 2 из предыдущего примера. Когда откроется диалоговое окно Списки (Custom Lists), в одноимённом столбце слева нажмите НОВЫЙ СПИСОК (NEW LIST) и заполните нужными значениями поле Элементы списка (List entries). Внимательно введите элементы Вашего списка именно в том порядке, в котором они должны быть расположены в результате сортировки.

Нажмите Добавить (Add), и созданный Вами список будет добавлен к уже существующим. Далее нажмите ОК .

Вот так выглядит наш список домашних дел, упорядоченных по важности:

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

Сортировать данные можно по тексту (от А к Я или от Я к А), числам (от наименьших к наибольшим или от наибольших к наименьшим), а также датам и времени (от старых к новым или от новых к старым) в одном или нескольких столбцах. Можно также выполнять сортировку по настраиваемым спискам, которые создаете вы сами (например, списку, состоящему из элементов "Большой", "Средний" и "Маленький"), или по формату, включая цвет ячеек и цвет шрифта, а также по значкам.

Примечания:

Сортировка текстовых значений

Примечания: Возможные проблемы

Сортировка чисел

Примечания:

Сортировка значений даты и времени

Примечания: Возможные проблемы

Сортировка по нескольким столбцам или строкам

Возможно, вы захотите выполнить сортировку по двум или нескольким столбцам или строкам, чтобы сгруппировать данные с одинаковыми значениями в одном столбце или строке, а затем отсортировать эти группы с одинаковыми значениями по другому столбцу или строке. Например, если есть столбцы "Отдел" и "Сотрудник", можно сначала выполнить сортировку по столбцу "Отдел" (для группировки всех сотрудников по отделам), а затем - по имени (для расположения имен сотрудников каждого отдела в алфавитном порядке). Можно одновременно выполнять сортировку по 64 столбцам.

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

Сортировка по цвету ячейки, цвету шрифта или значку

Если диапазон ячеек или столбец таблицы был отформатирован вручную или с помощью условного форматирования с использованием цвета ячеек или цвета шрифта, можно также выполнить сортировку по цветам. Кроме того, можно выполнить сортировку по набору значков, созданных с помощью условного форматирования.

Сортировка по настраиваемым спискам

Для сортировки в порядке, заданном пользователем, можно применять настраиваемые списки. Например, столбец может содержать значения, по которым вы хотите выполнить сортировку, такие как "Высокий", "Средний" и "Низкий". Как настроить сортировку, чтобы сначала отображались значения "Высокий", затем - "Средний", а в конце - "Низкий"? Если отсортировать их в алфавитном порядке (от А до Я), то значения "Высокий" будут отображаться вверху, но за ними окажутся значения "Низкий", а не "Средний". А при сортировке от Я до А значения "Средний" окажутся в самом верху. В действительности значения "Средний" всегда, независимо от порядка сортировки должны отображаться в середине. Вы можете решить эту проблему, создав настраиваемый список.

Сортировка с учетом регистра

Сортировка слева направо

Как правило, сортировка выполняется сверху вниз, однако значения можно отсортировать слева направо.

Примечание: Таблицы не поддерживают возможность сортировки слева направо. Сначала преобразуйте таблицу в диапазон: выделите в ней любую ячейку и выберите пункты Работа с таблицами > Преобразовать в диапазон .

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

Сортировка по части значения в столбце

Чтобы выполнить сортировку по части значения в столбце, например части кода (789-WDG -34), фамилии (Регина Покровская) или имени (Покровская Регина), сначала необходимо разбить столбец на две или несколько частей таким образом, чтобы значение, по которому нужно выполнить сортировку, оказалось в собственном столбце. Чтобы разбить значения в ячейке на части, можно воспользоваться текстовыми функциями или мастером текстов. Дополнительные сведения и примеры см. в статьях Разбивка текста по разным ячейкам и Разбивка текста по разным столбцам с помощью функций .

Сортировка меньшего диапазона в пределах большего

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

К счастью, Excel выдает предупреждение, если обнаруживает подобную попытку:

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

Если результат не соответствует желаемому, нажмите кнопку Отменить .

Примечание: Отсортировать подобным образом значения в таблице нельзя.

Дополнительные сведения об основных проблемах с сортировкой

Если результаты сортировки данных не соответствуют ожиданиям, сделайте следующее.

Проверьте, не изменились ли значения, возвращаемые формулами Если сортируемые данные содержат одну или несколько формул, значения, возвращаемые ими, при пересчете листа могут измениться. В этом случае примените сортировку повторно, чтобы получить актуальные результаты.

Перед сортировкой отобразите скрытые строки и столбцы При сортировке по столбцам скрытые строки не перемещаются, а при сортировке по строкам не перемещаются скрытые столбцы. Перед сортировкой данных целесообразно отобразить скрытые строки и столбцы.

Проверьте текущий параметр языкового стандарта Порядок сортировки зависит от выбранного языка. Убедитесь в том, что на панели управления в разделе Региональные параметры или Язык и региональные стандарты задан правильный языковой стандарт. Сведения о том, как изменить параметр языкового стандарта, см. в справке Microsoft Windows.

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

Включите или отключите строку заголовков Обычно рекомендуется отображать строку заголовков при сортировке по столбцам, так как она облегчает восприятие данных. По умолчанию значение в заголовке не включается в сортировку. Но в некоторых случаях может потребоваться включить или отключить заголовок, чтобы значение в заголовке включалось или не включалось в сортировку. Выполните одно из следующих действий.

    Чтобы исключить из сортировки первую строку данных (заголовок столбца), на вкладке Главная в группе Редактирование нажмите кнопку Сортировка и фильтр , выберите команду Настраиваемая сортировка и установите флажок .

    Чтобы включить в сортировку первую строку данных (так как она не является заголовком столбца), на вкладке Главная в группе Редактирование нажмите кнопку Сортировка и фильтр , выберите команду Настраиваемая сортировка и снимите флажок Мои данные содержат заголовки .

Создадим массив, в котором после завершения алгоритма будет лежать ответ. Будем поочередно вставлять элементы из исходного массива так, чтобы элементы в массиве-ответе всегда были отсортированы. Асимптотика в среднем и худшем случае – O(n 2), в лучшем – O(n). Реализовывать алгоритм удобнее по-другому (создавать новый массив и реально что-то вставлять в него относительно сложно): просто сделаем так, чтобы отсортирован был некоторый префикс исходного массива, вместо вставки будем менять текущий элемент с предыдущим, пока они стоят в неправильном порядке.

Реализация:

void insertionsort(int* l, int* r) { for (int *i = l + 1; i < r; i++) { int* j = i; while (j > l && *(j - 1) > *j) { swap(*(j - 1), *j); j--; } } }

Сортировка Шелла / Shellsort

Используем ту же идею, что и сортировка с расческой, и применим к сортировке вставками. Зафиксируем некоторое расстояние. Тогда элементы массива разобьются на классы – в один класс попадают элементы, расстояние между которыми кратно зафиксированному расстоянию. Отсортируем сортировкой вставками каждый класс. В отличие от сортировки расческой, неизвестен оптимальный набор расстояний. Существует довольно много последовательностей с разными оценками. Последовательность Шелла – первый элемент равен длине массива, каждый следующий вдвое меньше предыдущего. Асимптотика в худшем случае – O(n 2). Последовательность Хиббарда – 2 n - 1, асимптотика в худшем случае – O(n 1,5), последовательность Седжвика (формула нетривиальна, можете ее посмотреть по ссылке ниже) - O(n 4/3), Пратта (все произведения степеней двойки и тройки) - O(nlog 2 n). Отмечу, что все эти последовательности нужно рассчитать только до размера массива и запускать от большего от меньшему (иначе получится просто сортировка вставками). Также я провел дополнительное исследование и протестировал разные последовательности вида s i = a * s i - 1 + k * s i - 1 (отчасти это было навеяно эмпирической последовательностью Циура – одной из лучших последовательностей расстояний для небольшого количества элементов). Наилучшими оказались последовательности с коэффициентами a = 3, k = 1/3; a = 4, k = 1/4 и a = 4, k = -1/5.

Несколько полезных ссылок:

Реализации:

void shellsort(int* l, int* r) { int sz = r - l; int step = sz / 2; while (step > < r; i++) { int *j = i; int *diff = j - step; while (diff >= l && *diff > *j) { swap(*diff, *j); j = diff; diff = j - step; } } step /= 2; } } void shellsorthib(int* l, int* r) { int sz = r - l; if (sz <= 1) return; int step = 1; while (step < sz) step <<= 1; step >>= 1; step--; while (step >= 1) { for (int *i = l + step; i < r; i++) { int *j = i; int *diff = j - step; while (diff >= l && *diff > *j) { swap(*diff, *j); j = diff; diff = j - step; } } step /= 2; } } int steps; void shellsortsedgwick(int* l, int* r) { int sz = r - l; steps = 1; int q = 1; while (steps * 3 < sz) { if (q % 2 == 0) steps[q] = 9 * (1 << q) - 9 * (1 << (q / 2)) + 1; else steps[q] = 8 * (1 << q) - 6 * (1 << ((q + 1) / 2)) + 1; q++; } q--; for (; q > < r; i++) { int *j = i; int *diff = j - step; while (diff >= l && *diff > *j) { swap(*diff, *j); j = diff; diff = j - step; } } } } void shellsortpratt(int* l, int* r) { int sz = r - l; steps = 1; int cur = 1, q = 1; for (int i = 1; i < sz; i++) { int cur = 1 << i; if (cur > sz / 2) break; for (int j = 1; j < sz; j++) { cur *= 3; if (cur > sz / 2) break; steps = cur; } } insertionsort(steps, steps + q); q--; for (; q >= 0; q--) { int step = steps[q]; for (int *i = l + step; i < r; i++) { int *j = i; int *diff = j - step; while (diff >= l && *diff > *j) { swap(*diff, *j); j = diff; diff = j - step; } } } } void myshell1(int* l, int* r) { int sz = r - l, q = 1; steps = 1; while (steps < sz) { int s = steps; steps = s * 4 + s / 4; } q--; for (; q >= 0; q--) { int step = steps[q]; for (int *i = l + step; i < r; i++) { int *j = i; int *diff = j - step; while (diff >= l && *diff > *j) { swap(*diff, *j); j = diff; diff = j - step; } } } } void myshell2(int* l, int* r) { int sz = r - l, q = 1; steps = 1; while (steps < sz) { int s = steps; steps = s * 3 + s / 3; } q--; for (; q >= 0; q--) { int step = steps[q]; for (int *i = l + step; i < r; i++) { int *j = i; int *diff = j - step; while (diff >= l && *diff > *j) { swap(*diff, *j); j = diff; diff = j - step; } } } } void myshell3(int* l, int* r) { int sz = r - l, q = 1; steps = 1; while (steps < sz) { int s = steps; steps = s * 4 - s / 5; } q--; for (; q >= 0; q--) { int step = steps[q]; for (int *i = l + step; i < r; i++) { int *j = i; int *diff = j - step; while (diff >= l && *diff > *j) { swap(*diff, *j); j = diff; diff = j - step; } } } }

Сортировка деревом / Tree sort

Будем вставлять элементы в двоичное дерево поиска. После того, как все элементы вставлены достаточно обойти дерево в глубину и получить отсортированный массив. Если использовать сбалансированное дерево, например красно-черное, асимптотика будет равна O(nlogn) в худшем, среднем и лучшем случае. В реализации использован контейнер multiset.

Реализация:

void treesort(int* l, int* r) { multiset m; for (int *i = l; i < r; i++) m.insert(*i); for (int q: m) *l = q, l++; }

Гномья сортировка / Gnome sort

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

Реализация:

void gnomesort(int* l, int* r) { int *i = l; while (i < r) { if (i == l || *(i - 1) <= *i) i++; else swap(*(i - 1), *i), i--; } }

Сортировка выбором / Selection sort

На очередной итерации будем находить минимум в массиве после текущего элемента и менять его с ним, если надо. Таким образом, после i-ой итерации первые i элементов будут стоять на своих местах. Асимптотика: O(n 2) в лучшем, среднем и худшем случае. Нужно отметить, что эту сортировку можно реализовать двумя способами – сохраняя минимум и его индекс или просто переставляя текущий элемент с рассматриваемым, если они стоят в неправильном порядке. Первый способ оказался немного быстрее, поэтому он и реализован.

Реализация:

void selectionsort(int* l, int* r) { for (int *i = l; i < r; i++) { int minz = *i, *ind = i; for (int *j = i + 1; j < r; j++) { if (*j < minz) minz = *j, ind = j; } swap(*i, *ind); } }

Пирамидальная сортировка / Heapsort

Развитие идеи сортировки выбором. Воспользуемся структурой данных «куча» (или «пирамида», откуда и название алгоритма). Она позволяет получать минимум за O(1), добавляя элементы и извлекая минимум за O(logn). Таким образом, асимптотика O(nlogn) в худшем, среднем и лучшем случае. Реализовывал кучу я сам, хотя в С++ и есть контейнер priority_queue, поскольку этот контейнер довольно медленный.

Реализация:

template class heap { public: int size() { return n; } int top() { return h; } bool empty() { return n == 0; } void push(T a) { h.push_back(a); SiftUp(n); n++; } void pop() { n--; swap(h[n], h); h.pop_back(); SiftDown(0); } void clear() { h.clear(); n = 0; } T operator (int a) { return h[a]; } private: vector h; int n = 0; void SiftUp(int a) { while (a) { int p = (a - 1) / 2; if (h[p] > h[a]) swap(h[p], h[a]); else break; a--; a /= 2; } } void SiftDown(int a) { while (2 * a + 1 < n) { int l = 2 * a + 1, r = 2 * a + 2; if (r == n) { if (h[l] < h[a]) swap(h[l], h[a]); break; } else if (h[l] <= h[r]) { if (h[l] < h[a]) { swap(h[l], h[a]); a = l; } else break; } else if (h[r] < h[a]) { swap(h[r], h[a]); a = r; } else break; } } }; void heapsort(int* l, int* r) { heap h; for (int *i = l; i < r; i++) h.push(*i); for (int *i = l; i < r; i++) { *i = h.top(); h.pop(); } }

Быстрая сортировка / Quicksort

Выберем некоторый опорный элемент. После этого перекинем все элементы, меньшие его, налево, а большие – направо. Рекурсивно вызовемся от каждой из частей. В итоге получим отсортированный массив, так как каждый элемент меньше опорного стоял раньше каждого большего опорного. Асимптотика: O(nlogn) в среднем и лучшем случае, O(n 2). Наихудшая оценка достигается при неудачном выборе опорного элемента. Моя реализация этого алгоритма совершенно стандартна, идем одновременно слева и справа, находим пару элементов, таких, что левый элемент больше опорного, а правый меньше, и меняем их местами. Помимо чистой быстрой сортировки, участвовала в сравнении и сортировка, переходящая при малом количестве элементов на сортировку вставками. Константа подобрана тестированием, а сортировка вставками - наилучшая сортировка, подходящая для этой задачи (хотя не стоит из-за этого думать, что она самая быстрая из квадратичных).

Реализация:

void quicksort(int* l, int* r) { if (r - l <= 1) return; int z = *(l + (r - l) / 2); int* ll = l, *rr = r - 1; while (ll <= rr) { while (*ll < z) ll++; while (*rr > z) rr--; if (ll <= rr) { swap(*ll, *rr); ll++; rr--; } } if (l < rr) quicksort(l, rr + 1); if (ll < r) quicksort(ll, r); } void quickinssort(int* l, int* r) { if (r - l <= 32) { insertionsort(l, r); return; } int z = *(l + (r - l) / 2); int* ll = l, *rr = r - 1; while (ll <= rr) { while (*ll < z) ll++; while (*rr > z) rr--; if (ll <= rr) { swap(*ll, *rr); ll++; rr--; } } if (l < rr) quickinssort(l, rr + 1); if (ll < r) quickinssort(ll, r); }

Сортировка слиянием / Merge sort

Сортировка, основанная на парадигме «разделяй и властвуй». Разделим массив пополам, рекурсивно отсортируем части, после чего выполним процедуру слияния: поддерживаем два указателя, один на текущий элемент первой части, второй – на текущий элемент второй части. Из этих двух элементов выбираем минимальный, вставляем в ответ и сдвигаем указатель, соответствующий минимуму. Слияние работает за O(n), уровней всего logn, поэтому асимптотика O(nlogn). Эффективно заранее создать временный массив и передать его в качестве аргумента функции. Эта сортировка рекурсивна, как и быстрая, а потому возможен переход на квадратичную при небольшом числе элементов.

Реализация:

void merge(int* l, int* m, int* r, int* temp) { int *cl = l, *cr = m, cur = 0; while (cl < m && cr < r) { if (*cl < *cr) temp = *cl, cl++; else temp = *cr, cr++; } while (cl < m) temp = *cl, cl++; while (cr < r) temp = *cr, cr++; cur = 0; for (int* i = l; i < r; i++) *i = temp; } void _mergesort(int* l, int* r, int* temp) { if (r - l <= 1) return; int *m = l + (r - l) / 2; _mergesort(l, m, temp); _mergesort(m, r, temp); merge(l, m, r, temp); } void mergesort(int* l, int* r) { int* temp = new int; _mergesort(l, r, temp); delete temp; } void _mergeinssort(int* l, int* r, int* temp) { if (r - l <= 32) { insertionsort(l, r); return; } int *m = l + (r - l) / 2; _mergeinssort(l, m, temp); _mergeinssort(m, r, temp); merge(l, m, r, temp); } void mergeinssort(int* l, int* r) { int* temp = new int; _mergeinssort(l, r, temp); delete temp; }

Сортировка подсчетом / Counting sort

Создадим массив размера r – l, где l – минимальный, а r – максимальный элемент массива. После этого пройдем по массиву и подсчитаем количество вхождений каждого элемента. Теперь можно пройти по массиву значений и выписать каждое число столько раз, сколько нужно. Асимптотика – O(n + r - l). Можно модифицировать этот алгоритм, чтобы он стал стабильным: для этого определим место, где должно стоять очередное число (это просто префиксные суммы в массиве значений) и будем идти по исходному массиву слева направо, ставя элемент на правильное место и увеличивая позицию на 1. Эта сортировка не тестировалась, поскольку большинство тестов содержало достаточно большие числа, не позволяющие создать массив требуемого размера. Однако она, тем не менее, пригодилась.

Блочная сортировка / Bucket sort

(также известна как корзинная и карманная сортировка). Пусть l – минимальный, а r – максимальный элемент массива. Разобьем элементы на блоки, в первом будут элементы от l до l + k, во втором – от l + k до l + 2k и т.д., где k = (r – l) / количество блоков. В общем-то, если количество блоков равно двум, то данный алгоритм превращается в разновидность быстрой сортировки. Асимптотика этого алгоритма неясна, время работы зависит и от входных данных, и от количества блоков. Утверждается, что на удачных данных время работы линейно. Реализация этого алгоритма оказалась одной из самых трудных задач. Можно сделать это так: просто создавать новые массивы, рекурсивно их сортировать и склеивать. Однако такой подход все же довольно медленный и меня не устроил. В эффективной реализации используется несколько идей:

1) Не будем создавать новых массивов. Для этого воспользуемся техникой сортировки подсчетом – подсчитаем количество элементов в каждом блоке, префиксные суммы и, таким образом, позицию каждого элемента в массиве.

2) Не будем запускаться из пустых блоков. Занесем индексы непустых блоков в отдельный массив и запустимся только от них.

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

4) Поскольку алгоритм получился довольно громоздким, при небольшом количестве элементов он крайне неэффективен. До такой степени, что переход на сортировку вставками ускоряет работу примерно в 10 раз.

Осталось только понять, какое количество блоков нужно выбрать. На рандомизированных тестах мне удалось получить следующую оценку: 1500 блоков для 10 7 элементов и 3000 для 10 8 . Подобрать формулу не удалось – время работы ухудшалось в несколько раз.

Реализация:

void _newbucketsort(int* l, int* r, int* temp) { if (r - l <= 64) { insertionsort(l, r); return; } int minz = *l, maxz = *l; bool is_sorted = true; for (int *i = l + 1; i < r; i++) { minz = min(minz, *i); maxz = max(maxz, *i); if (*i < *(i - 1)) is_sorted = false; } if (is_sorted) return; int diff = maxz - minz + 1; int numbuckets; if (r - l <= 1e7) numbuckets = 1500; else numbuckets = 3000; int range = (diff + numbuckets - 1) / numbuckets; int* cnt = new int; for (int i = 0; i <= numbuckets; i++) cnt[i] = 0; int cur = 0; for (int* i = l; i < r; i++) { temp = *i; int ind = (*i - minz) / range; cnt++; } int sz = 0; for (int i = 1; i <= numbuckets; i++) if (cnt[i]) sz++; int* run = new int; cur = 0; for (int i = 1; i <= numbuckets; i++) if (cnt[i]) run = i - 1; for (int i = 1; i <= numbuckets; i++) cnt[i] += cnt; cur = 0; for (int *i = l; i < r; i++) { int ind = (temp - minz) / range; *(l + cnt) = temp; cur++; cnt++; } for (int i = 0; i < sz; i++) { int r = run[i]; if (r != 0) _newbucketsort(l + cnt, l + cnt[r], temp); else _newbucketsort(l, l + cnt[r], temp); } delete run; delete cnt; } void newbucketsort(int* l, int* r) { int *temp = new int; _newbucketsort(l, r, temp); delete temp; }

Поразрядная сортировка / Radix sort

(также известна как цифровая сортировка). Существует две версии этой сортировки, в которых, на мой взгляд, мало общего, кроме идеи воспользоваться представлением числа в какой-либо системе счисления (например, двоичной).
LSD (least significant digit):
Представим каждое число в двоичном виде. На каждом шаге алгоритма будем сортировать числа таким образом, чтобы они были отсортированы по первым k * i битам, где k – некоторая константа. Из данного определения следует, что на каждом шаге достаточно стабильно сортировать элементы по новым k битам. Для этого идеально подходит сортировка подсчетом (необходимо 2 k памяти и времени, что немного при удачном выборе константы). Асимптотика: O(n), если считать, что числа фиксированного размера (а в противном случае нельзя было бы считать, что сравнение двух чисел выполняется за единицу времени). Реализация довольно проста.

Реализация:

int digit(int n, int k, int N, int M) { return (n >> (N * k) & (M - 1)); } void _radixsort(int* l, int* r, int N) { int k = (32 + N - 1) / N; int M = 1 << N; int sz = r - l; int* b = new int; int* c = new int[M]; for (int i = 0; i < k; i++) { for (int j = 0; j < M; j++) c[j] = 0; for (int* j = l; j < r; j++) c++; for (int j = 1; j < M; j++) c[j] += c; for (int* j = r - 1; j >= l; j--) b[--c] = *j; int cur = 0; for (int* j = l; j < r; j++) *j = b; } delete b; delete c; } void radixsort(int* l, int* r) { _radixsort(l, r, 8); }

MSD (most significant digit):
На самом деле, некоторая разновидность блочной сортировки. В один блок будут попадать числа с равными k битами. Асимптотика такая же, как и у LSD версии. Реализация очень похожа на блочную сортировку, но проще. В ней используется функция digit, определенная в реализации LSD версии.

Реализация:

void _radixsortmsd(int* l, int* r, int N, int d, int* temp) { if (d == -1) return; if (r - l <= 32) { insertionsort(l, r); return; } int M = 1 << N; int* cnt = new int; for (int i = 0; i <= M; i++) cnt[i] = 0; int cur = 0; for (int* i = l; i < r; i++) { temp = *i; cnt++; } int sz = 0; for (int i = 1; i <= M; i++) if (cnt[i]) sz++; int* run = new int; cur = 0; for (int i = 1; i <= M; i++) if (cnt[i]) run = i - 1; for (int i = 1; i <= M; i++) cnt[i] += cnt; cur = 0; for (int *i = l; i < r; i++) { int ind = digit(temp, d, N, M); *(l + cnt) = temp; cur++; cnt++; } for (int i = 0; i < sz; i++) { int r = run[i]; if (r != 0) _radixsortmsd(l + cnt, l + cnt[r], N, d - 1, temp); else _radixsortmsd(l, l + cnt[r], N, d - 1, temp); } delete run; delete cnt; } void radixsortmsd(int* l, int* r) { int* temp = new int; _radixsortmsd(l, r, 8, 3, temp); delete temp; }

Битонная сортировка / Bitonic sort:

Идея данного алгоритма заключается в том, что исходный массив преобразуется в битонную последовательность – последовательность, которая сначала возрастает, а потом убывает. Ее можно эффективно отсортировать следующим образом: разобьем массив на две части, создадим два массива, в первый добавим все элементы, равные минимуму из соответственных элементов каждой из двух частей, а во второй – равные максимуму. Утверждается, что получатся две битонные последовательности, каждую из которых можно рекурсивно отсортировать тем же образом, после чего можно склеить два массива (так как любой элемент первого меньше или равен любого элемента второго). Для того, чтобы преобразовать исходный массив в битонную последовательность, сделаем следующее: если массив состоит из двух элементов, можно просто завершиться, иначе разделим массив пополам, рекурсивно вызовем от половинок алгоритм, после чего отсортируем первую часть по порядку, вторую в обратном порядке и склеим. Очевидно, получится битонная последовательность. Асимптотика: O(nlog 2 n), поскольку при построении битонной последовательности мы использовали сортировку, работающую за O(nlogn), а всего уровней было logn. Также заметим, что размер массива должен быть равен степени двойки, так что, возможно, придется его дополнять фиктивными элементами (что не влияет на асимптотику).

Реализация:

void bitseqsort(int* l, int* r, bool inv) { if (r - l <= 1) return; int *m = l + (r - l) / 2; for (int *i = l, *j = m; i < m && j < r; i++, j++) { if (inv ^ (*i > *j)) swap(*i, *j); } bitseqsort(l, m, inv); bitseqsort(m, r, inv); } void makebitonic(int* l, int* r) { if (r - l <= 1) return; int *m = l + (r - l) / 2; makebitonic(l, m); bitseqsort(l, m, 0); makebitonic(m, r); bitseqsort(m, r, 1); } void bitonicsort(int* l, int* r) { int n = 1; int inf = *max_element(l, r) + 1; while (n < r - l) n *= 2; int* a = new int[n]; int cur = 0; for (int *i = l; i < r; i++) a = *i; while (cur < n) a = inf; makebitonic(a, a + n); bitseqsort(a, a + n, 0); cur = 0; for (int *i = l; i < r; i++) *i = a; delete a; }

Timsort

Гибридная сортировка, совмещающая сортировку вставками и сортировку слиянием. Разобьем элементы массива на несколько подмассивов небольшого размера, при этом будем расширять подмассив, пока элементы в нем отсортированы. Отсортируем подмассивы сортировкой вставками, пользуясь тем, что она эффективно работает на отсортированных массивах. Далее будем сливать подмассивы как в сортировке слиянием, беря их примерно равного размера (иначе время работы приблизится к квадратичному). Для этого удобного хранить подмассивы в стеке, поддерживая инвариант - чем дальше от вершины, тем больше размер, и сливать подмассивы на верхушке только тогда, когда размер третьего по отдаленности от вершины подмассива больше или равен сумме их размеров. Асимптотика: O(n) в лучшем случае и O(nlogn) в среднем и худшем случае. Реализация нетривиальна, твердой уверенности в ней у меня нет, однако время работы она показала довольно неплохое и согласующееся с моими представлениями о том, как должна работать эта сортировка.

Подробнее timsort описан здесь:

Реализация:

void _timsort(int* l, int* r, int* temp) { int sz = r - l; if (sz <= 64) { insertionsort(l, r); return; } int minrun = sz, f = 0; while (minrun >= 64) { f |= minrun & 1; minrun >>= 1; } minrun += f; int* cur = l; stack> s; while (cur < r) { int* c1 = cur; while (c1 < r - 1 && *c1 <= *(c1 + 1)) c1++; int* c2 = cur; while (c2 < r - 1 && *c2 >= *(c2 + 1)) c2++; if (c1 >= c2) { c1 = max(c1, cur + minrun - 1); c1 = min(c1, r - 1); insertionsort(cur, c1 + 1); s.push({ c1 - cur + 1, cur }); cur = c1 + 1; } else { c2 = max(c2, cur + minrun - 1); c2 = min(c2, r - 1); reverse(cur, c2 + 1); insertionsort(cur, c2 + 1); s.push({ c2 - cur + 1, cur }); cur = c2 + 1; } while (s.size() >= 3) { pair x = s.top(); s.pop(); pair y = s.top(); s.pop(); pair z = s.top(); s.pop(); if (z.first >= x.first + y.first && y.first >= x.first) { s.push(z); s.push(y); s.push(x); break; } else if (z.first >= x.first + y.first) { merge(y.second, x.second, x.second + x.first, temp); s.push(z); s.push({ x.first + y.first, y.second }); } else { merge(z.second, y.second, y.second + y.first, temp); s.push({ z.first + y.first, z.second }); s.push(x); } } } while (s.size() != 1) { pair x = s.top(); s.pop(); pair y = s.top(); s.pop(); if (x.second < y.second) swap(x, y); merge(y.second, x.second, x.second + x.first, temp); s.push({ y.first + x.first, y.second }); } } void timsort(int* l, int* r) { int* temp = new int; _timsort(l, r, temp); delete temp; }

Тестирование

Железо и система

Процессор: Intel Core i7-3770 CPU 3.40 GHz
ОЗУ: 8 ГБ
Тестирование проводилось на почти чистой системе Windows 10 x64, установленной за несколько дней до запуска. Использованная IDE – Microsoft Visual Studio 2015.

Тесты

Все тесты поделены на четыре группы. Первая группа – массив случайных чисел по разным модулям (10, 1000, 10 5 , 10 7 и 10 9). Вторая группа – массив, разбивающийся на несколько отсортированных подмассивов. Фактически брался массив случайных чисел по модулю 10 9 , а далее отсортировывались подмассивы размера, равного минимуму из длины оставшегося суффикса и случайного числа по модулю некоторой константы. Последовательность констант – 10, 100, 1000 и т.д. вплоть до размера массива. Третья группа – изначально отсортированный массив случайных чисел с некоторым числом «свопов» - перестановок двух случайных элементов. Последовательность количеств свопов такая же, как и в предыдущей группе. Наконец, последняя группа состоит из нескольких тестов с полностью отсортированным массивом (в прямом и обратном порядке), нескольких тестов с исходным массивом натуральных чисел от 1 до n, в котором несколько чисел заменены на случайное, и тестов с большим количеством повторений одного элемента (10%, 25%, 50%, 75% и 90%). Таким образом, тесты позволяют посмотреть, как сортировки работают на случайных и частично отсортированных массивах, что выглядит наиболее существенным. Четвертая группа во многом направлена против сортировок с линейным временем работы, которые любят последовательности случайных чисел. В конце статьи есть ссылка на файл, в котором подробно описаны все тесты.

Размер входных данных

Было бы довольно глупо сравнивать, например, сортировку с линейным временем работы и квадратичную, и запускать их на тестах одного размера. Поэтому каждая из групп тестов делится еще на четыре группы, размера 10 5 , 10 6 , 10 7 и 10 8 элементов. Сортировки были разбиты на три группы, в первой – квадратичные (сортировка пузырьком, вставками, выбором, шейкерная и гномья), во второй – нечто среднее между логарифмическим временем и квадратом, (битонная, несколько видов сортировки Шелла и сортировка деревом), в третьей все остальные. Кого-то, возможно, удивит, что сортировка деревом попала не в третью группу, хотя ее асимптотика и O(nlogn), но, к сожалению, ее константа очень велика. Сортировки первой группы тестировались на тестах с 10 5 элементов, второй группы – на тестах с 10 6 и 10 7 , третьей – на тестах с 10 7 и 10 8 . Именно такие размеры данных позволяют как-то увидеть рост времени работы, при меньших размерах слишком велика погрешность, при больших алгоритм работает слишком долго (или же недостаток оперативной памяти). С первой группой я не стал заморачиваться, чтобы не нарушать десятикратное увеличение (10 4 элементов для квадратичных сортировок слишком мало), в конце концов, сами по себе они представляют мало интереса.

Как проводилось тестирование

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

Тонкости реализации

Возможно, кого-то удивит, что в реализации самого процесса тестирования я не использовал указатели на функции, что сильно сократило бы код. Оказалось, что это заметно замедляет работу алгоритма (примерно на 5-10%). Поэтому я использовал отдельный вызов каждой функции (это, конечно, не отразилось бы на относительной скорости, но… все же хочется улучшить и абсолютную). По той же причине были заменены векторы на обычные массивы, не были использованы шаблоны и функции-компараторы. Все это более актуально для промышленного использования алгоритма, нежели его тестирования.

Результаты

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

Поскольку картинок очень много, они скрыты спойлерами. Немного комментариев по поводу обозначений. Сортировки названы так, как выше, если это сортировка Шелла, то в скобочках указан автор последовательности, к названиям сортировок, переходящих на сортировку вставками, приписано Ins (для компактности). В диаграммах у второй группы тестов обозначена возможная длина отсортированных подмассивов, у третьей группы - количество свопов, у четвертой - количество замен. Общий результат рассчитывался как среднее по четырем группам.

Первая группа сортировок

Массив случайных чисел

Таблицы









Совсем скучные результаты, даже частичная отсортированность при небольшом модуле почти незаметна.

Таблицы









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

Свопы

Таблицы











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

Изменения в перестановке

Таблицы










Группа почти ничем не отличается от предыдущей, поэтому результаты похожи. Однако сортировка пузырьком вырывается вперед, так как случайный элемент, вставленный в массив, скорее всего будет больше всех остальных, то есть за одну итерацию переместится в конец. Сортировка выбором стала аутсайдером.

Повторы

Таблицы










Здесь все сортировки (кроме, конечно, сортировки выбором) работали почти одинаково, ускоряясь по мере увеличении количества повторов.

Итоговые результаты

За счет своего абсолютного безразличия к массиву, сортировка выбором, работавшая быстрее всех на случайных данных, все же проиграла сортировке вставками. Гномья сортировка оказалась заметно хуже последней, из-за чего ее практическое применение сомнительно. Шейкерная и пузырьковая сортировки оказались медленнее всех.

Вторая группа сортировок

Массив случайных чисел

Таблицы, 1е6 элементов










Сортировка Шелла с последовательностью Пратта ведет себя совсем странно, остальные более менее ясно. Сортировка деревом любит частично отсортированные массивы, но не любит повторов, возможно, поэтому самое худшее время работы именно посередине.

Таблицы, 1е7 элементов










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

Частично отсортированный массив

Таблицы, 1е6 элементов









Здесь понятным образом ведут себя все сортировки, кроме Шелла с Хиббардом, который почему-то не сразу начинает ускоряться.

Таблицы, 1е7 элементов










Здесь все, в общем, как и прежде. Даже асимптотика сортировки деревом не помогла ей вырваться с последнего места.

Свопы

Таблицы, 1е6 элементов










Таблицы, 1е7 элементов









Здесь заметно, что у сортировок Шелла большая зависимость от частичной отсортированности, так как они ведут себя практически линейно, а остальные две только сильно падают на последних группах.

Изменения в перестановке

Таблицы, 1е6 элементов










Таблицы, 1е7 элементов










Здесь все похоже на предыдущую группу.

Повторы

Таблицы, 1е6 элементов










Опять все сортировки продемонстрировали удивительную сбалансированность, даже битонная, которая, казалось бы, почти не зависит от массива.

Таблицы, 1е7 элементов










Ничего интересного.

Итоговые результаты

Убедительное первое место заняла сортировка Шелла по Хиббарду, не уступив ни в одной промежуточной группе. Возможно, стоило ее отправить в первую группу сортировок, но… она слишком слаба для этого, да и тогда почти никого не было бы в группе. Битонная сортировка довольно уверенно заняла второе место. Третье место при миллионе элементах заняла другая сортировка Шелла, а при десяти миллионах сортировка деревом (асимптотика сказалась). Стоит обратить внимание, что при десятикратном увеличении размера входных данных все алгоритмы, кроме древесной сортировки, замедлились почти в 20 раз, а последняя всего лишь в 13.

Третья группа сортировок

Массив случайных чисел

Таблицы, 1е7 элементов






















Таблицы, 1е8 элементов













Почти все сортировки этой группы имеют почти одинаковую динамику. Почему же почти все сортировки ускоряются, когда массив частично отсортирован? Обменные сортировки работают быстрее потому, что надо делать меньше обменов, в сортировке Шелла выполняется сортировка вставками, которая сильно ускоряется на таких массивах, в пирамидальной сортировке при вставке элементов сразу завершается просеивание, в сортировке слиянием выполняется в лучшем случае вдвое меньше сравнений. Блочная сортировка работает тем лучше, чем меньше разность между минимальным и максимальным элементом. Принципиально отличается только поразрядная сортировка, которой все это безразлично. LSD-версия работает тем лучше, чем больший модуль. Динамика MSD-версия мне не ясна, то, что она сработала быстрее чем LSD удивило.

Частично отсортированный массив

Таблицы, 1е7 элементов












Таблицы, 1е8 элементов












Здесь все тоже довольно понятно. Стало заметен алгоритм Timsort, на него отсортированность действует сильнее, чем на остальные. Это позволило этому алгоритму почти сравняться с оптимизированной версией быстрой сортировки. Блочная сортировка, несмотря на улучшение времени работы при частичной отсортированности, не смогла обогнать поразрядную сортировку. Ноутбуки