# Задача о Ханойских башнях В сортировке слиянием и построении двоичного дерева мы разбиваем исходную задачу на две подзадачи, размер которых составляет половину исходной задачи. Однако для задачи о Ханойских башнях мы используем другую стратегию разбиения. !!! question Даны три стержня, обозначенные как `A`, `B` и `C`. В начальном состоянии на стержне `A` находится $n$ дисков, расположенных сверху вниз в порядке возрастания размера. Наша задача -- переместить эти $n$ дисков на стержень `C`, сохранив их исходный порядок (как показано на рисунке ниже). При перемещении дисков необходимо соблюдать следующие правила. 1. Диск можно снять только с верхушки одного стержня и поместить на верхушку другого стержня. 2. За один раз можно переместить только один диск. 3. Меньший диск всегда должен находиться над большим диском. ![Пример задачи о Ханойских башнях](../assets/hanota_example.png) **Обозначим задачу о Ханойских башнях размера $i$ как $f(i)$**. Например, $f(3)$ представляет задачу о Ханойских башнях по перемещению $3$ дисков с `A` на `C`. ### Рассмотрение базового случая Как показано на рисунке ниже, для задачи $f(1)$, то есть когда имеется только один диск, мы можем просто переместить его с `A` на `C`. === "<1>" ![Решение задачи размера 1](../assets/hanota_f1_step1.png) === "<2>" ![hanota_f1_step2](../assets/hanota_f1_step2.png) Как показано на рисунке ниже, для задачи $f(2)$, то есть когда имеется два диска, **поскольку необходимо всегда соблюдать условие, что меньший диск находится над большим, нужно использовать `B` для завершения перемещения**. 1. Сначала переместить верхний меньший диск с `A` на `B`. 2. Затем переместить больший диск с `A` на `C`. 3. Наконец, переместить меньший диск с `B` на `C`. === "<1>" ![Решение задачи размера 2](../assets/hanota_f2_step1.png) === "<2>" ![hanota_f2_step2](../assets/hanota_f2_step2.png) === "<3>" ![hanota_f2_step3](../assets/hanota_f2_step3.png) === "<4>" ![hanota_f2_step4](../assets/hanota_f2_step4.png) Процесс решения задачи $f(2)$ можно обобщить следующим образом: **переместить два диска с `A` на `C`, используя `B`**. При этом `C` называется целевым стержнем, а `B` -- буферным стержнем. ### Разбиение на подзадачи Для задачи $f(3)$, то есть когда имеется три диска, ситуация становится немного сложнее. Поскольку решения $f(1)$ и $f(2)$ уже известны, мы можем мыслить с точки зрения стратегии «разделяй и властвуй», **рассматривая два верхних диска на `A` как единое целое**, и выполнить шаги, показанные на рисунке ниже. Таким образом, три диска успешно перемещаются с `A` на `C`. 1. Сделать `B` целевым стержнем, а `C` -- буферным стержнем, и переместить два диска с `A` на `B`. 2. Переместить оставшийся один диск с `A` непосредственно на `C`. 3. Сделать `C` целевым стержнем, а `A` -- буферным стержнем, и переместить два диска с `B` на `C`. === "<1>" ![Решение задачи размера 3](../assets/hanota_f3_step1.png) === "<2>" ![hanota_f3_step2](../assets/hanota_f3_step2.png) === "<3>" ![hanota_f3_step3](../assets/hanota_f3_step3.png) === "<4>" ![hanota_f3_step4](../assets/hanota_f3_step4.png) По сути, **мы разбиваем задачу $f(3)$ на две подзадачи $f(2)$ и одну подзадачу $f(1)$**. После последовательного решения этих трех подзадач исходная задача также решается. Это показывает, что подзадачи независимы, и их решения можно объединить. Таким образом, мы можем обобщить стратегию «разделяй и властвуй» для решения задачи о Ханойских башнях, показанную на рисунке ниже: разбить исходную задачу $f(n)$ на две подзадачи $f(n-1)$ и одну подзадачу $f(1)$, и решить эти три подзадачи в следующем порядке. 1. Переместить $n-1$ дисков с `A` на `B`, используя `C`. 2. Переместить оставшийся $1$ диск непосредственно с `A` на `C`. 3. Переместить $n-1$ дисков с `B` на `C`, используя `A`. Для этих двух подзадач $f(n-1)$ **можно применить рекурсивное разбиение тем же способом**, пока не будет достигнута наименьшая подзадача $f(1)$. А решение $f(1)$ известно -- требуется только одна операция перемещения. ![Стратегия «разделяй и властвуй» для решения задачи о Ханойских башнях](../assets/hanota_divide_and_conquer.png) ### Реализация кода В коде мы объявляем рекурсивную функцию `dfs(i, src, buf, tar)`, которая перемещает $i$ дисков с верхушки стержня `src` на целевой стержень `tar`, используя буферный стержень `buf`: ```src [file]{hanota}-[class]{}-[func]{solve_hanota} ``` Как показано на рисунке ниже, задача о Ханойских башнях формирует рекурсивное дерево высотой $n$, где каждый узел представляет подзадачу, соответствующую вызову функции `dfs()`, **поэтому временная сложность составляет $O(2^n)$, а пространственная сложность -- $O(n)$**. ![Рекурсивное дерево задачи о Ханойских башнях](../assets/hanota_recursive_tree.png) !!! quote Задача о Ханойских башнях происходит из древней легенды. В храме в древней Индии у монахов было три высоких алмазных стержня и $64$ золотых диска разного размера. Монахи постоянно перемещали диски, веря, что в тот момент, когда последний диск будет правильно размещен, этот мир закончится. Однако, даже если монахи будут перемещать диски каждую секунду, потребуется примерно $2^{64} \approx 1.84×10^{19}$ секунд, что составляет около $5850$ миллиардов лет, что намного превышает текущую оценку возраста Вселенной. Поэтому, если эта легенда правдива, нам не стоит беспокоиться о конце света.