Сортировка «Ханойская башня»

1entv_rmxpqkppxjfjktg259tak.jpeg


Ханойские башни
Про знаменитую игру Эдуарда Люка́ на Хабре не писа́л только ленивый. Кажется, все покровы сорваны и что-то ещё по поводу алгоритма добавить уже невозможно. Но нет, у данной темы есть ещё скрытые ресурсы. Сегодня, в частности, мы переделаем алгоритм решения этой головоломки в полноценную сортировку. (Зачем? Just for fun. В пятницу можно.)

Данный пост посвящается памяти истинного гуру программирования Андрея Mrrl Астрелина, который когда-то мне просто и доходчиво объяснил этот алгоритм. Псевдокод ниже по тексту — его авторства.

mqskm1nurwmw6erlsjhuzepusia.gif
В классической головоломке изначально диски на первом шесте уже упорядочены. Но мы разрешим им быть нанизанными в любом порядке.

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

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

Остальные условия мы (почти) не меняем:

  • У нас два вспомогательных шеста (пара пустых массивов).
  • Можем переносить диски по одному.
  • Класть только меньшие на бо́льшие (раз мы разрешили одинаковые размеры дисков, то также можно класть перемещаемый диск сверху на другие такого же размера).
  • Имеем право сравнивать переносимый диск только с самым верхними дисками (то бишь, все 3 массива являются стеками).


Наша задача: взять классический рекурсивный алгоритм головоломки…

def hanoi(n, source, helper, target):
        if n > 0:
                hanoi(n - 1, source, target, helper)
                if source:
                        target.append(source.pop())
                hanoi(n - 1, helper, source, target)


… и превратить его в сортировку!

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

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

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

Что касается разрешения повторов, то это вообще не имеет никакого значения. Потому что подряд идущие одинаковые диски мы просто перемещаем как один диск.

Алгоритм


Назовем столбики A, B, C (A в начале непустой).

Введем операции:

АС() — переложить один диск с А на С.
top (A), top© — размер верхнего диска А или С. Если столбик пуст, то этот размер = MaxInt.
ВС(К) — переложить с В на С все диски, размер которых меньше К (мы это можем сделать, если верхние диски А и С не меньше К).
swap() — переставить столбики В и С (или переименовать их — нам ведь все равно, где окажутся диски).
while(A) — цикл пока А не пуст.

Тогда работает такой алгоритм:

//С каждым перемещённым диском с шеста A всё более восстанавливаем "ханойскую" систему
while(A) {
        K = top(A);
        //Мини-"ханой" для группы дисков
        while(top(C) < K){
                B->C(top(C));
                swap();
        }
        A->C();
}

//Система восстановлена. Завершение - классический "ханой"
while(C) {
        B->C(top(C));
        swap();
}


© Mrrl

Сложность


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

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

Реализация


Свой вариант на Python пока не написал (сделаю это позже). Однако ниже в разделе «Ссылки» накидал несколько ссылок, по которым можно посмотреть имплементации на различных языках. Особенно интересен вариант на Джаве. Автор не стал брать за основу общеизвестный рекурсивный способ для решения головоломки, а построил кратчайшее дерево путей. Предположительно, это является самым эффективным решением, если писать сортировку в стиле «Ханойской башни».

Характеристики алгоритма

Название Сортировка «Ханойская башня», Tower of Hanoi sort
Автор идеи Эдуард Люка́
Класс Сортировки вставками
Сравнения Есть
Временна́я сложность лучшая ?
средняя ?
худшая O (2n)

Ссылки


3ywqmhuo7fv68jggkc416kbzuw4.pngTower of Hanoi / Ханойская башня

Реализации: Си, Java vs C++ vs Python, Python.

Статьи серии:


В приложении AlgoLab данная сортировка теперь доступна. Хотя она относится к классу сортировок вставками, по причине экстравагантности алгоритма она помещена в раздел «Прочие сортировки». Ещё там есть ограничение — числа в сортируемом массиве могут быть только от 1 до 5 (связано с непростой прорисовкой дисков). Другие числа всё равно будут заменены на эти.

Также есть ограничение на размер массива — не более 20-ти элементов. Сделано это сугубо в Ваших же интересах — если возьмёте слишком большой массив, то, может так статься, что сортировать его придётся до глубокой старости.

EDISON Software - web-development
Статья написана при поддержке компании EDISON Software, которая профессионально занимается разработкой для web и недавно сделала редизайн своего сайта

© Habrahabr.ru