Ханойские башни
Задача, решаемая тремя строками — если поверить в решение для n−1. Разбор индукции, из которой оно получается.
4 мин
Три стержня, на первом — дисков разного диаметра, сложенных пирамидой: больший всегда под меньшим. Переложить всю пирамиду на третий стержень.
Правила: за ход перемещается один диск, только верхний со стержня, и класть больший на меньший нельзя.
Задача выглядит запутанной, если пытаться придумать последовательность ходов напрямую. Индукцией она решается в три строки.
Индукция
База. Один диск переносится одним ходом.
Переход. Пусть мы умеем переносить дисков с любого стержня на любой. Тогда дисков переносятся так:
- перенести верхние дисков на промежуточный стержень;
- переложить самый большой диск на целевой;
- перенести дисков с промежуточного на целевой.
Почему шаг 1 законен, хотя внизу лежит большой диск: он самый большой из всех, значит, ни один диск не может оказаться под ним неправильно. Для перекладывания верхних его как будто нет.
Шаг 2 законен, потому что целевой стержень к этому моменту пуст.
Шаг 3 — то же предположение индукции.
flowchart LR
A["n дисков<br/>на стержне A"] -->|"шаг 1<br/>рекурсия для n−1"| B["n−1 на B,<br/>большой на A"]
B -->|"шаг 2<br/>один ход"| C["n−1 на B,<br/>большой на C"]
C -->|"шаг 3<br/>рекурсия для n−1"| D["n дисков<br/>на стержне C"]
Индукция замкнулась: решение существует всегда, для любого .
Код
vector<pair<int, int>> moves;
int thirdPeg(int from, int to) { return 3 - from - to; } // стержни 0, 1, 2
void hanoi(int from, int to, int count) {
if (count == 0) return;
int middle = thirdPeg(from, to);
hanoi(from, middle, count - 1);
moves.push_back({from, to});
hanoi(middle, to, count - 1);
}
Функция thirdPeg возвращает единственный стержень, не совпадающий с двумя данными. Приём 3 - from - to работает, потому что сумма всех трёх номеров равна ; писать шесть условий не нужно.
Проверено: для от 1 до 16 последовательность корректна (ни один диск не кладётся на меньший), пирамида собирается на третьем стержне в правильном порядке, и число ходов равно ровно .
Число ходов
Из рекурсии сразу следует , откуда .
Это и минимально возможное число ходов: чтобы переложить нижний диск, надо освободить его стержень, то есть увести дисков на промежуточный, а это не меньше ходов; после его перекладывания их надо вернуть — ещё .
Практическое следствие: выводить сами ходы можно лишь при примерно до 25. Для больших задачи спрашивают только их количество — а это уже формула, а не перебор.
Чему это учит
Ханойские башни — лучший пример двух вещей.
Иногда правильно не понимать решение целиком. Никто не держит в голове порядок из тысячи ходов. Достаточно поверить, что для решение есть, — и построить шаг.
Рекурсия строит не только числа, но и ответы. Здесь она выдаёт последовательность действий, накапливая её в общем массиве. Тот же приём — в восстановлении ответа в динамике и в выводе пути в графе.
И наоборот: попытка расписать ходы «руками», по шаблону, ни к чему не приводит. Задача разрешима именно потому, что её удаётся свести к себе же меньшего размера.
Родственные задачи
Четыре стержня. Задача становится заметно сложнее: оптимальное число ходов даётся алгоритмом Фрейма—Стюарта, а его оптимальность доказана лишь недавно.
Найти -й ход, не выписывая остальные. Решается рассуждением: первые ходов — это рекурсия для , следующий — большой диск, дальше опять рекурсия. Спуск по этой структуре занимает .
Восстановить, где какой диск после ходов. Тот же спуск, только следим за положением каждого диска.
Все три — упражнения на ту же индукцию, и все три решаются тем, что вы понимаете структуру последовательности ходов, а не саму последовательность.