Вот реализация на Haskell (обновление: позаботилась о случае с 3 колышками, сделав k = n-1, когда r = 3):
-- hanoi for n disks and r pegs [p1, p2, ..., pr]
hanoiR :: Int -> [a] -> [(a, a)]
-- zero disks: no moves needed.
hanoiR 0 _ = []
-- one disk: one move and two pegs needed.
hanoiR 1 (p1 : p2 : rest) = [(p1, p2)] -- only needed for smart-alecks?
{-
-- n disks and 3 pegs -- unneeded; covered by (null rest) below.
hanoiR n [p1, p2, p3] =
hanoiR (n - 1) [p1, p3, p2] ++
[(p1, p2)] ++
hanoiR (n - 1) [p3, p2, p1]
-}
-- n disks and r > 3 pegs: use Frame-Stewart algorithm
hanoiR n (p1 : p2 : p3 : rest) =
hanoiR k (p1 : p3 : p2 : rest) ++
hanoiR (n - k) (p1 : p2 : rest) ++
hanoiR k (p3 : p2 : p1 : rest)
where k
| null rest = n - 1
| otherwise = n `quot` 2
Так что загрузите это в GHCi и введите
hanoiR 4 [1, 2, 3, 4]
т.е. запустить Ханойские башни с 4 дисками и 4 колышками. Вы можете назвать 4 колышка как хотите, например
hanoiR 4 ['a', 'b', 'c', 'd']
Выход:
[(1,2),(1,3),(2,3),(1,4),(1,2),(4,2),(3,1),(3,2),(1,2)]
т.е. переместите верхний диск из колышка 1 в колышек 2, затем верхний диск из колышка 1 в колышек 3 и т. д.
Я довольно новичок в Хаскеле, поэтому должен признать, что горжусь тем, что это работает. Но у меня могут быть глупые ошибки, поэтому обратная связь приветствуется.
Как видно из кода, эвристика для k - это просто floor (n / 2). Я не пытался оптимизировать k, хотя n / 2 показалось мне удачным.
Я проверил правильность ответа для 4 дисков и 4 колышков. Слишком поздно для меня, чтобы проверить больше, без написания симулятора. (@ _ @) Вот еще несколько результатов:
ghci> hanoiR 6 [1, 2, 3, 4, 5]
[(1,2),(1,4),(1,3),(4,3),(2,3),(1,4),(1,5),(1,2),
(5,2),(4,2),(3,1),(3,4),(3,2),(4,2),(1,2)]
ghci> hanoiR 6 [1, 2, 3, 4]
[(1,2),(1,4),(1,3),(4,3),(2,3),(1,2),(1,4),(2,4),(1,2),
(4,1),(4,2),(1,2),(3,1),(3,4),(3,2),(4,2),(1,2)]
ghci> hanoiR 8 [1, 2, 3, 4, 5]
[(1,3),(1,2),(3,2),(1,4),(1,3),(4,3),(2,1),(2,3),(1,3),(1,2),
(1,4),(2,4),(1,5),(1,2),(5,2),(4,1),(4,2),(1,2),
(3,2),(3,1),(2,1),(3,4),(3,2),(4,2),(1,3),(1,2),(3,2)]
Это проясняет алгоритм?
Действительно существенная часть
hanoiR k (p1 : (p3 : (p2 : rest))) ++ -- step 1; corresponds to T(k,r)
hanoiR (n-k) (p1 : (p2 : rest)) ++ -- step 2; corresponds to T(n-k, r-1)
hanoiR k (p3 : (p2 : (p1 : rest))) -- step 3; corresponds to T(k,r)
, где мы объединяем последовательности ходов для шагов 1, 2 и 3 алгоритма Фрейма-Стюарта. Чтобы определить ходы, мы комментируем шаги F-S следующим образом:
- Обычно, когда вызывается Ханой, цель определяется (без потери общности) как перенос дисков с первого штифта на второй, используя все оставшиеся штыри для временного хранения. Мы используем это соглашение при повторении, чтобы определить источник, место назначения и разрешенное хранилище разделенных и побежденных подзадач.
- Таким образом, исходный колышек равен p1, а конечный колышек - p2. Все оставшиеся колышки доступны как временное хранилище для проблемы Ханоя верхнего уровня.
- Шаг 1, «Для некоторых k, 1 <= k <n, перенести верхние k дисков на один другой колышек»: мы выбираем p3 в качестве «одного другого колышка». </li>
- Таким образом, «не нарушая колышек, который теперь содержит верхние k дисков» (шаг 2), означает рекурсивно использовать все колышки, кроме p3. То есть р1, р2, а остальные за р3.
- «Перенести верхние k дисков на конечный колышек» (шаг 3) означает переход с «другого колышка» (p3) на p2.
Это имеет смысл?