1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

# теория · шаг 1 из 7

Устойчивость и сортировка по ключу

Коротко

Обычно сортируют не голые числа, а ЗАПИСИ по одному их полю - ключу (людей по возрасту, заказы по дате). Устойчивая сортировка сохраняет исходный порядок записей с ОДИНАКОВЫМ ключом; неустойчивая - может их переставить. Зачем это важно: устойчивость позволяет СОБИРАТЬ сортировку по нескольким полям из простых. Чтобы упорядочить по «фамилии, затем имени», сортируют сначала по имени, потом УСТОЙЧИВО по фамилии - второй проход не рушит порядок по имени внутри одинаковых фамилий. Устойчивы: вставками, слияние, подсчётом. Неустойчивы: выбором, быстрая, пирамидальная, Шелла.

Если ты знаешь, что такое ключ и устойчивость, умеешь сортировать по нескольким полям через устойчивость (от менее важного ключа к более важному) и помнишь, какие сортировки устойчивы, листай до «Теперь сам».

Как думаешь, что именно сравнивают, когда сортируют реальные данные

В жизни сортируют не числа сами по себе, а ЗАПИСИ: список сотрудников, заказов, файлов. И упорядочивают их по какому-то одному полю - по возрасту, по дате, по имени. Это поле и называют ключом. Сортировка сравнивает записи ТОЛЬКО по ключу, а всё остальное поле несёт с собой как «груз». И тут возникает вопрос, которого нет у голых чисел: а что делать с записями, у которых ключ ОДИНАКОВЫЙ? Их можно оставить как были, а можно переставить - и от этого выбора зависит очень многое.

На что это похоже

Стопка личных дел, которую надо разложить по годам рождения. Два человека родились в один год - в каком порядке класть их дела? «Устойчивый» архивариус оставит их в том порядке, в каком они лежали (скажем, по алфавиту, как было раньше). «Неустойчивый» - бросит как попало. Пока тебе нужен только год - разницы нет. Но если дела УЖЕ были разложены по алфавиту, а ты сверху раскладываешь по годам, то устойчивость сохранит алфавит внутри каждого года - и ты получишь порядок «по году, а внутри года по алфавиту» БЕСПЛАТНО.

Механизм: ключ, устойчивость и композиция

Ключ. Сортировка сравнивает записи по значению ключа (запись.возраст, запись[0]), а перемещает запись целиком. Одну и ту же структуру можно сортировать по разным ключам.

Устойчивость. Сортировка УСТОЙЧИВА, если записи с равным ключом сохраняют свой исходный взаимный порядок. Формально: если во входе запись A стояла раньше B и их ключи равны, то и в выходе A раньше B.

Композиция по нескольким ключам. Чтобы отсортировать по «первичному ключу, а внутри равных - по вторичному», делают два прохода УСТОЙЧИВОЙ сортировкой: сначала по ВТОРИЧНОМУ ключу, потом по ПЕРВИЧНОМУ. Второй проход расставляет первичный ключ, а устойчивость сохраняет уже наведённый порядок по вторичному внутри равных первичных.

после сорт. по баллу [2, 1][1, 3] [2, 5][1, 9] → устойчиво по классу → итог [1, 3][1, 9] [2, 1][2, 5] класс 1: балл 3 перед 9 - порядок по баллу сохранён
Второй проход расставил классы, а устойчивость сохранила порядок по баллу внутри каждого класса. Так из двух простых сортировок собирается двухключевая.

Правило

Ключ - поле, по которому сравнивают записи (перемещая их целиком). Сортировка УСТОЙЧИВА, если записи с равным ключом сохраняют исходный взаимный порядок. Устойчивость позволяет собирать многоключевую сортировку: сортируй по ключам от МЕНЕЕ важного к БОЛЕЕ важному устойчивой сортировкой. Устойчивы вставками, слияние и подсчётом; неустойчивы выбором, быстрая, пирамидальная и Шелла.

Разбор: почему для многоключевой сортировки идут от менее важного ключа

Порядок проходов - от ВТОРИЧНОГО (менее важного) к ПЕРВИЧНОМУ (более важному) - и он не случаен. Более важный ключ должен сортироваться ПОСЛЕДНИМ, чтобы задать главный порядок. А внутри записей с равным первичным ключом порядок берётся из ПРЕДЫДУЩЕГО прохода - по вторичному ключу, - и держится он только потому, что последний проход УСТОЙЧИВ. Проверь на записях [класс, балл]: сначала сортируем по баллу (вторичный) - получаем записи по возрастанию балла; потом устойчиво по классу (первичный) - классы встают по порядку, а внутри каждого класса баллы остаются возрастающими (устойчивость не тронула их взаимный порядок). Пойди мы в обратном порядке (сначала по классу, потом по баллу) - второй проход по баллу перемешал бы классы, и всё сломалось бы. Менее важный - раньше, более важный - последним и устойчиво: вот рецепт.

Разбор: какие сортировки устойчивы и почему

Устойчивость - свойство конкретного алгоритма, и зависит она от того, переставляет ли он ДАЛЁКИЕ элементы. Вставками устойчива: она сдвигает только элементы строго больше ключа (>), поэтому равные не перепрыгивают друг друга. Слияние устойчиво: при слиянии равных берут элемент из ЛЕВОЙ (более ранней) части. Подсчётом устойчива (в варианте с префиксными суммами). А вот быстрая, пирамидальная, выбором и Шелла - НЕустойчивы: все они меняют местами далёкие элементы (разбиение вокруг опорного, просеивание кучи, выбор минимума, прыжки Шелла), и равные при этом легко меняются местами. Практический вывод: нужна устойчивость - бери слияние (или встроенную сортировку объектов, она обычно устойчива); можно без неё - подойдёт и быстрая.

Что ломается без этого

Первая ошибка - взять НЕустойчивую сортировку для второго прохода многоключевой сортировки: порядок по предыдущему ключу разрушится, и внутри равных первичных ключей вторичный окажется перемешан. Именно эту ошибку допускает наивная попытка «отсортирую по классу» - она не сохраняет порядок по баллу. Вторая ошибка - думать, что устойчивость «всегда нужна» или «всегда бесплатна»: если ключ уникален (равных нет вовсе) или порядок равных не важен, устойчивость не нужна, и можно взять более быструю неустойчивую сортировку. Устойчивость - инструмент под задачу, а не всегдашнее требование.

Зачем это в работе

Многоключевая сортировка - повседневность: таблица, которую пользователь сортирует по одному столбцу, потом по другому, ожидая, что прошлый порядок сохранится (кликнул «по дате», потом «по имени» - записи с одинаковым именем остались по дате). Это работает ровно потому, что стандартная сортировка объектов УСТОЙЧИВА. В Python list.sort и sorted устойчивы и принимают key=: records.sort(key=...). Чтобы отсортировать по двум полям, можно либо передать ключом кортеж (первичный, вторичный), либо (когда так нельзя) сделать два устойчивых прохода от менее важного ключа к более важному. Понимать устойчивость - значит уметь получать сложные порядки, не написав ни одной сложной сортировки.

Теперь сам

Записи [город, имя] надо упорядочить по городу, а внутри города - по имени. В каком порядке делать проходы устойчивой сортировкой и почему именно так, а не наоборот?

Ответ. Сначала по ИМЕНИ (вторичный, менее важный ключ), потом УСТОЙЧИВО по ГОРОДУ (первичный, более важный). После первого прохода записи упорядочены по имени. Второй проход расставляет города по порядку, а устойчивость сохраняет порядок по имени внутри каждого города - выходит «по городу, а внутри по имени». Наоборот нельзя: если сначала отсортировать по городу, а потом по имени, то второй проход (по имени) перемешает города между собой, и порядок по городу разрушится. Правило: менее важный ключ - раньше, более важный - последним и устойчивой сортировкой.

Главное

Ключ - поле, по которому сравнивают записи (перемещая их целиком). Сортировка УСТОЙЧИВА, если записи с равным ключом сохраняют исходный взаимный порядок. Устойчивость нужна, чтобы собирать многоключевую сортировку: сортируй по ключам от МЕНЕЕ важного к БОЛЕЕ важному устойчивой сортировкой - последний (главный) проход не рушит порядок, наведённый предыдущими. Устойчивы вставками, слияние, подсчётом; неустойчивы выбором, быстрая, пирамидальная, Шелла (они переставляют далёкие элементы). Нужна устойчивость - бери слияние; не нужна - подойдёт быстрая.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

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