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

# теория · шаг 2 из 6

Сортировка вставками: адаптивная и устойчивая

Коротко

Сортировка вставками берёт элементы по одному и вставляет каждый на своё место в уже отсортированную левую часть, сдвигая большие вправо, - как раскладывают карты в руке. В худшем случае (массив задом наперёд) - Θ(n²). Но на ПОЧТИ упорядоченных данных она блистает: если каждый элемент недалеко от места, внутренний сдвиг короткий, и выходит Θ(n). Это делает её адаптивной - её цена падает с упорядоченностью входа. Плюс она устойчивая (равные не меняются местами), работает на месте и «на лету» (можно сортировать поток по мере поступления).

Если ты умеешь сортировать вставками, знаешь, почему на почти упорядоченных данных это Θ(n), а в худшем Θ(n²), и что значит «адаптивная» и «устойчивая», листай до «Теперь сам».

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

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

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

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

Механизм: вставить в отсортированный префикс

Массив делится на отсортированный префикс слева (сначала это один первый элемент) и остаток справа. Берём первый элемент остатка - ключ - и вставляем его в префикс: идём справа налево по префиксу и, пока встречаем элементы БОЛЬШЕ ключа, сдвигаем их на одну позицию вправо; как только встретили элемент не больше ключа (или дошли до края) - ставим ключ в освободившееся место. Префикс вырос на один. Повторяем n − 1 раз.

35 префикс 2 ключ → 2 меньше 5 и 3, сдвигаем оба вправо → 235 префикс длиннее
Ключ идёт влево, сдвигая элементы больше себя, и встаёт на своё место. Чем ближе он к месту, тем меньше сдвигов.

Правило

Сортировка вставками вставляет каждый элемент в отсортированный префикс слева, сдвигая большие вправо. Худший случай (вход задом наперёд) - Θ(n²); лучший (почти упорядоченный) - Θ(n), потому что сдвигать почти нечего. Она АДАПТИВНА (цена падает с упорядоченностью входа), УСТОЙЧИВА (равные сохраняют порядок) и работает на месте. Это лучшая из простых сортировок и рабочая лошадка на маленьких и почти упорядоченных массивах.

Разбор: почему на почти упорядоченных данных это Θ(n)

Вся работа вставки - во внутреннем сдвиге: сколько элементов префикса больше ключа, столько сдвигов. Если массив уже отсортирован, то каждый новый ключ не меньше всего префикса - внутренний цикл сразу видит, что сдвигать нечего, и делает 0 сдвигов. Остаётся только n − 1 внешних шагов и по одному сравнению на каждый - Θ(n). Если массив ПОЧТИ упорядочен (каждый элемент не дальше k позиций от своего места), то каждый ключ сдвигает не больше k элементов - всего Θ(n·k), и при маленьком k это практически линия. Вот формальный смысл слов «выигрывает на почти упорядоченных»: её цена - не константа n², а величина, пропорциональная БЕСПОРЯДКУ во входе (числу пар, стоящих не по порядку). Мало беспорядка - мало работы.

Разбор: худший случай и устойчивость

Худший вход - строго убывающий, например [5, 4, 3, 2, 1]. Тогда каждый новый ключ меньше всего префикса и проваливается в самое начало, сдвигая ВЕСЬ префикс: сдвигов 1 + 2 + ... + (n−1) = Θ(n²). Это потолок. Теперь про устойчивость: во внутреннем цикле мы сдвигаем элементы СТРОГО большие ключа (> ключ), а на равном (= ключ) останавливаемся и ставим ключ ПОСЛЕ него. Значит два равных элемента никогда не перепрыгнут друг через друга - их взаимный порядок сохранится. Это и есть устойчивость, и держится она на одном знаке: сравнивать надо >, а не ≥. Поставь ≥ - сортировка останется верной, но потеряет устойчивость (равные будут меняться местами).

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

Первая ошибка - взять ≥ вместо > во внутреннем сравнении и молча потерять устойчивость: массив отсортируется верно, но равные элементы поменяются местами, и это всплывёт позже, когда устойчивость окажется нужна (сортировка по второму ключу - см. последнюю главу). Вторая - считать сортировку вставками «просто ещё одним квадратом» и не пользоваться её адаптивностью: именно из-за неё вставками сортируют маленькие куски внутри быстрых сортировок и почти готовые данные. Третья - применить её к большому СЛУЧАЙНОМУ массиву: там беспорядок максимален, и Θ(n²) бьёт в полную силу.

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

Сортировка вставками живёт внутри промышленных сортировок. Быстрая сортировка и сортировка слиянием на маленьких подмассивах (обычно ≤ 10-20 элементов) переключаются именно на вставки: у неё крохотные накладные расходы, а на коротком куске квадрат неважен. Алгоритм Timsort (стандартная сортировка в Python и Java для объектов) построен вокруг того, что реальные данные ЧАСТО почти упорядочены: он ищет уже упорядоченные куски и сшивает их, а короткие досортировывает вставками - и на почти готовых данных выходит близко к Θ(n). То есть адаптивность вставки - не academic курьёз, а причина, по которой стандартная сортировка так быстра на реальных, почти упорядоченных входах.

Теперь сам

Сравни, сколько СДВИГОВ сделает сортировка вставками на [1, 2, 3, 4] и на [4, 3, 2, 1]. Почему разница именно такая и как это связано со словом «адаптивная»?

Ответ. На [1, 2, 3, 4] каждый новый ключ (2, 3, 4) не меньше всего префикса, внутренний цикл сразу останавливается - 0 сдвигов, всего 3 сравнения, Θ(n). На [4, 3, 2, 1] каждый ключ меньше всего префикса и проваливается в начало: ключ 3 сдвигает 1 элемент, 2 сдвигает 2, 1 сдвигает 3 - всего 1 + 2 + 3 = 6 сдвигов, Θ(n²). Разница ровно в беспорядке входа: в первом случае его нет (0 сдвигов), во втором он максимален (каждая пара стоит не по порядку). Это и значит «адаптивная»: одна и та же сортировка стоит Θ(n) на упорядоченном и Θ(n²) на перевёрнутом - её цена подстраивается под то, насколько вход близок к отсортированному.

Главное

Сортировка вставками вставляет каждый элемент в отсортированный префикс, сдвигая большие вправо. Худший случай (перевёрнутый вход) - Θ(n²), лучший (почти упорядочен) - Θ(n), потому что сдвигать почти нечего: её цена пропорциональна беспорядку входа (числу пар не по порядку). Поэтому она АДАПТИВНА. Она же УСТОЙЧИВА - если во внутреннем сравнении стоит >, а не ≥: равные не перепрыгивают друг друга. Лучшая из простых, живёт внутри быстрых сортировок на маленьких кусках и в основе Timsort.

Комментарии

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

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

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