Подтвердить что ты не робот

Башни Ханоя с колышками K

Проблема Towers of Hanoi - классическая проблема для рекурсии. Вам дается 3 привязки с дисками на одном из них, и вы должны переместить все диски с одной привязки на другую, следуя приведенным правилам. Вы также должны сделать это с минимальным количеством ходов.

Здесь рекурсивный алгоритм, который решает проблему:

void Hanoi3(int nDisks, char source, char intermed, char dest)
{
    if( nDisks > 0 )
    {
        Hanoi3(nDisks - 1, source, dest, intermed);
        cout << source << " --> " << dest << endl;
        Hanoi3(nDisks - 1, intermed, source, dest);
    }
}


int main()
{
    Hanoi3(3, 'A', 'B', 'C');

    return 0;
}

Теперь представьте ту же проблему, только с 4 привязками, поэтому мы добавляем еще одну промежуточную привязку. Когда вы сталкиваетесь с проблемой выбора того, какую промежуточную привязку выбрать в любой точке, мы выберем самый левый, если более 1 свободен.

У меня есть следующий рекурсивный алгоритм для этой проблемы:

void Hanoi4(int nDisks, char source, char intermed1, char intermed2, char dest)
{
    if ( nDisks == 1 )
        cout << source << " --> " << dest << endl;
    else if ( nDisks == 2 )
    {
        cout << source << " --> " << intermed1 << endl;
        cout << source << " --> " << dest << endl;
        cout << intermed1 << " --> " << dest << endl;
    }
    else
    {
        Hanoi4(nDisks - 2, source, intermed2, dest, intermed1);
        cout << source << " --> " << intermed2 << endl;
        cout << source << " --> " << dest << endl;
        cout << intermed2 << " --> " << dest << endl;
        Hanoi4(nDisks - 2, intermed1, source, intermed2, dest);
    }
}

int main()
{
    Hanoi4(3, 'A', 'B', 'C', 'D');

    return 0;
}

Теперь, мой вопрос заключается в том, как бы я обобщил этот рекурсивный подход для работы с привязками K? Рекурсивная функция получит char[], которая будет содержать метки каждого стека, поэтому функция будет выглядеть примерно так:

void HanoiK(int nDisks, int kStacks, char labels[]) { ... }

Я знаю о алгоритме Frame-Stewart, который, скорее всего, оптимален, но не доказан, и который дает вам число ходов. Однако меня интересует строго рекурсивное решение, которое следует за образцом рекурсивных решений для 3 и 4 привязок, что означает, что он печатает фактические ходы.

Для меня, по крайней мере, псевдокод алгоритма Frame-Stewart, представленный в Википедии, довольно абстрактен, и мне не удалось преобразовать его в код, который печатает ходы. Я бы согласился с эталонной реализацией этого (для случайного K) или даже более подробного псевдокода.

Я попытался придумать какой-то алгоритм, который соответствующим образом изменяет массив меток, но мне не удавалось заставить его работать. Любые предложения приветствуются.

Update:

Кажется, что намного проще решить на функциональном языке. Здесь реализация F #, основанная на решении LarsH Haskell:

let rec HanoiK n pegs = 
    if n > 0 then 
        match pegs with
        | p1::p2::rest when rest.IsEmpty            
            ->  printfn "%A --> %A" p1 p2
        | p1::p2::p3::rest when rest.IsEmpty        
            ->  HanoiK (n-1) (p1::p3::p2::rest)
                printfn "%A --> %A" p1 p2
                HanoiK (n-1) (p3::p2::p1::rest)    
        | p1::p2::p3::rest when not rest.IsEmpty    
            ->  let k = int(n / 2)
                HanoiK k (p1::p3::p2::rest)
                HanoiK (n-k) (p1::p2::rest)
                HanoiK k (p3::p2::p1::rest)

let _ =
    HanoiK 6 [1; 2; 3; 4; 5; 6]

И без обработки 3 привязок в качестве краевого случая:

let rec HanoiK n pegs = 
    if n > 0 then 
        match pegs with
        | p1::p2::rest when rest.IsEmpty            
            ->  printfn "%A --> %A" p1 p2
        | p1::p2::p3::rest     
            ->  let k = if rest.IsEmpty then n - 1 else int(n / 2) 
                HanoiK k (p1::p3::p2::rest)
                HanoiK (n-k) (p1::p2::rest)
                HanoiK k (p3::p2::p1::rest)

Заметим, что это не обрабатывает вырожденные случаи, для которых нет решения, например HanoiK 2 [1; 2]

4b9b3361

Ответ 1

Вот реализация в Haskell (обновление: позаботился о случае 3-peg, сделав 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 привязки, что хотите, например

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 и т.д.

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

Как видно из кода, эвристика для k - это просто пол (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 алгоритма Frame-Stewart. Чтобы определить ходы, мы аннотируем шаги ФС следующим образом:

  • Обычно, когда вызывается ханой, цель определяется (без потери общности) как перенос дисков с первой привязки ко второй привязке, используя все остальные привязки для временного хранения. Мы используем это соглашение при рекурсии, чтобы определить источник, назначение и разрешить хранение подзадач с разделением и покорением.
  • Таким образом, исходный привязкой является p1, а целевой привязкой является p2. Все остальные привязки доступны как временное хранилище для проблемы ханоя верхнего уровня.
  • Шаг 1: "Для некоторых k, 1 <= k <n, перенесите верхние k-диски на один другой привязку": мы выберем p3 как "один другой привязку".
  • Таким образом, "не нарушая привязку, которая теперь содержит верхние k-диски" (шаг 2), означает рекурсивно использовать все привязки, кроме p3. Ie p1, p2, а остальные - p3.
  • "Перенести верхние k-диски в целевую привязку" (шаг 3) означает перенос с "другого привязки" (p3) на p2.

Имеет ли это смысл?

Ответ 2

Чтобы решить Башни Ханоя, все, что вам нужно сделать, это:

Алгоритм Frame Stewart на самом деле не такой сложный. По существу, вам нужно переместить определенное количество дисков (например, половина из них) на некоторый привязку: относиться к этим дискам, как к их собственной, отдельной башне. Легко определить решение для 1 или 2 дисков, а вы переместите первую половину в пункт назначения, вы переместите вторую половину в нужное место.

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

Кроме того, если k >= 3, вы можете решить его точно так же, как 3-х опорные башни Ханоя, просто игнорируя остальные колышки, хотя это не оптимально.

Ответ 3

Хинзе, Ральф. Функциональная жемчужина: La Tour D'Hanoi, http://www.comlab.ox.ac.uk/ralf.hinze/publications/ICFP09.pdf

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

Ответ 4

Головоломка "Башни Ханой" была опубликована в западном мире в 1883 году французским математиком Эдуардом Лукасом под псевдонимом Н. Лукас де Сиам. В "легенде", которая сопровождала игру, говорилось, что в Бенаресе, во время правления императора Фо Хи, был индийский храм с куполом, который был центром мира (Каши Вишванат). Внутри купола священники (брамины) перемещали золотые диски между тремя алмазными острицами (изношенные столбы), локтем высотой и толщиной с телом пчелы. Бог (Брахма) разместил 64 золотых диска на одной иголке во время творения. (Диски были перемещены в соответствии с неизменными законами от Брахмы, чтобы перенести их с одной привязки на другую). Было сказано, что когда они завершат свою задачу, вселенная закончится. Легенда варьируется в зависимости от нескольких сайтов, но она в целом последовательна.    "Законы", установленные Брахмой, таковы: 1) Можно перемещать только один диск за один раз 2) На диск меньшего размера не может быть установлен диск 3) Только верхний диск может быть удален, а затем помещен в верхнюю часть другого штырька, и его диски Игра заканчивается, когда весь стек дисков перемещен в другой привязку. Было быстро обнаружено, что решение с 3-мя петлями существует, но ни один из них не решен для решения 4+ peg.   В 1939 году Американский математический ежемесячник провел конкурс для решения для и m peg и n дисков. Два года спустя два отдельных (но позже доказанных равных) алгоритма были опубликованы J. S. Frame и B. M. Stewart. Оба они еще не были признаны правильными, хотя они, как правило, считаются правильными. На правильном пути дальнейших достижений не было. ***** Это работает только на трех проблемах с привязкой *****   Было показано, что минимальное количество ходов для башни из n дисков было 2n-1 с простым рекурсивным решением следующим образом: Обозначьте начало, цель и темп. Чтобы переместить n колышек от начала привязки к привязке цели через временную привязку: При n > 1, (i) Рекурсивно перемещать верхние n-1 диски от начала до темпа через цель. (ii) Переместите n-й диск с начала на цель. (iii) Рекурсивно переместите n-1 диски из temp в цель через start. Это решение занимает 2n-1 ходов: (1) Если n = 1, f (n) = 1 = 2n-1 (2) Если n > 1, f (n) = 2 * (2n-1-1) +1 = 2n-2 + 1 = 2n-1   Легкий способ его решения... 1,3,7,15,31 - это решения для первых нескольких n дисков. Рекурсивно, что напоминает nk = 2nk-1 + 1. Отсюда мы можем индуцировать, что n = 2n-1. Доказательство по индукции я оставляю вам. ***** базовый алгоритм Frame/Stewart *****   Для m peg и n дисков выбирают l таким, что 0 ≤ l < n (тогда как l - целое число, которое минимизирует шаги в следующей настройке)... • Переместите верхние диски с начальной позиции на промежуточную; это может быть выполнено в Hk (l) ходах (так как нижние n-l диски вообще не мешают движению). • Переместите нижние n-l диски с начальной привязки на привязку цели с помощью движений Hk-1 (n-l). (Поскольку одна привязка занята башней меньших дисков, ее нельзя использовать на этом этапе.) • Переместите оригинальные l-диски из промежуточной привязки в привязку цели в Hk (l). Таким образом, по существу это Hk (n) = 2Hk (l) + Hk-1 (n-1) ----- > сведено к минимуму *****Проще простого!! Не!*****   Проверка его работы против нашего решения с 3 ключами должна быть простой. Используя k = 2; положим H2 (0) = 0, H2 (1) = 1 и H2 (n > 1) = ∞. При k = 3 можно положить l = n-1. (Это приводит к тому, что наш H2 (n-1) станет конечным). Это позволит нам написать H3 (n) = 2H3 (n-1) + H2 (1), равную 2n-1. Это немного игра слов, но она работает. ***** Несколько другое описание, которое поможет уточнить *****   Алгоритм Frame-Stewart, предоставляющий предположительно оптимальное решение для четырех (или даже более) штифтов, описан ниже: Определите H (n, m) как минимальное количество ходов, необходимых для передачи n дисков с использованием m pegs Алгоритм можно описать рекурсивно: 1. Для некоторых l, 1

`Option VBASupport 1
Option Explicit
Dim n as double
dim m as double
dim l as double
dim rx as double
dim rxtra as double
dim r as double
dim x as double
dim s1 as double
dim s2 as double
dim i as integer
dim a ()
dim b ()
dim c ()
dim d ()
dim aa as double
dim bb as double
dim cc as double
dim dd as double
dim total as double

Sub Hanoi
on error goto errorhandler

m=inputbox ("m# pegs=??")
n=inputbox ("n# discs=??")
x=-1
l=m-1
rx=1
s1=0
s2=0
aa=0

while n>rx
        x=x+1
        r=(l+x)/(x+1)
        rx=r*rx
wend
rx=1
for i=0 to x-1
        r=(l+i)/(i+1)
        rx=r*rx
        redim a (-1 to x)
        redim b (-1 to x)
        redim c (-1 to x)
        redim d (-1 to x)
            a(i)=rx
            b(i)=i
            bb=b(i)
            c(i)=rx-aa
            aa=a(i)
            cc=c(i)
            d(i)=cc*2^bb
            dd=d(i)
            s1=s1+dd
next

rxtra=n-aa
s2=rxtra*2^(bb+1)
total = 2*(s1+s2)-1
msgbox total

exit sub
errorhandler: msgbox "dang it!!"
'1, 3, 5, 9, 13, 17, 25, 33 first 8 answers for 4 peg
'16=161,25=577,32=1281,64=18433
End Sub`

Раскрытие: эти источники были использованы для подтверждения ответов и для некоторой истории проблемы. Трудно дать точный кредит, когда это связано с тем, что несколько сайтов использовались для проверки... так что все они являются источниками для многих частей истории.