Erlang 解決漢諾塔問題

最後一次更新於 2019/07/09算法

基本規則

對於只有三個塔的漢諾塔問題咱們有個基本規則函數

  1. 將全部圓盤從塔1轉移到塔3。
  2. 小圓盤只能放在大圓盤上面。
  3. 若是想移動某個特定的圓盤,必須先把其上的全部圓盤移走。

基本算法

根據以上規則,漢諾塔的算法能夠寫成如下幾個步驟:code

第一步: 將 N-1 個圓盤從初始塔移動到中間塔。orm

第二步: 再將該圓盤從初始塔移動到目的塔。遞歸

第三步: 再將剩下的 N-1 個圓盤從中間塔移動到目的塔。

(對每一個圓盤都進行上述操做那麼這個思路能夠看做是遞歸)源碼

漢諾塔的 Erlang 源碼

在 Erlang 中,咱們能夠將不一樣塔中的圓盤表達成 [{tower1, [5,4,3,2,1]},{tower2,[]},{tower3,[]}] 的形式。it

用戶惟一須要作的事情就是傳遞初始的圓盤總數。這個數字將被列表轉換成升序的數字形式。io

此函數以下所示:form

produce(1) -> [1]; % 若是當前數字爲1,就直接返回1,遞歸結束。
produce(N) ->
    produce(N-1) ++ [N]. % 將爲遞歸的數值放於當前數值以前。

舉個例子,若是總數爲5,通過 produce() 函數運行後的結果爲[1,2,3,4,5]。但這不是咱們想要的結果。

有人可能會提議把 produce(N-1) ++ [N] 語句掉換成 [N] ++ produce(N-1) 不就好了嗎。是的,若是按當前的要求看效果已經達到了。
然而實際上,咱們每次都會移動塔頂最上方的圓盤,它的值必定是列表中最小的。若是在這裏使用降序排列的話咱們獲取到的數值就會是5而不是1。
所以,reverse() 函數應該做爲一個額外調用的方法來寫。class

此函數以下所示:

reverse_tower([H|T],M)->
    reverse_tower(T,[H|M]); % 將新頭圓盤添加到列表中,列表將自動反轉。
reverse_tower([],M)-> M. % 若是列表爲空說明全部圓盤已排好序。
reverse_tower(T)->
    reverse_tower(T,[]). % 初始列表。

在列表轉換以後,咱們能夠開始經過上述算法解決問題。

該算法經過如下函數實現:

%% ==================================================================================================================
%% 此函數用於返回不一樣塔的狀態和須要移動的圓盤。
%% ==================================================================================================================
check_status(T, Start, End)->
    % 使用 _ 變量在模式匹配中做爲通配符。
    [{_,Tower1List},{_,Tower2List},{_,Tower3List}] = T,
    Status = if Start == tower1, End == tower2 -> % 添加新的圓盤到塔2並丟掉塔1最上方的圓盤。
                Number = hd(Tower1List), [{tower1,tl(Tower1List)},{tower2,[Number|Tower2List]},{tower3,Tower3List}];
              Start == tower1, End == tower3 -> % 添加新的圓盤到塔3並丟掉塔1最上方的圓盤。
                Number = hd(Tower1List), [{tower1,tl(Tower1List)},{tower2,Tower2List},{tower3,[Number|Tower3List]}];
              Start == tower2, End == tower1 -> % 添加新的圓盤到塔1並丟掉塔2最上方的圓盤。
                Number = hd(Tower2List), [{tower1,[Number|Tower1List]},{tower2,tl(Tower2List)},{tower3,Tower3List}];
              Start == tower2, End == tower3 -> % 添加新的圓盤到塔3並丟掉塔2最上方的圓盤。
                Number = hd(Tower2List), [{tower1,Tower1List},{tower2,tl(Tower2List)},{tower3,[Number|Tower3List]}];
              Start == tower3, End == tower1 -> % 添加新的圓盤到塔1並丟掉塔3最上方的圓盤。
                Number = hd(Tower3List), [{tower1,[Number|Tower1List]},{tower2, Tower2List},{tower3,tl(Tower3List)}];
              % 添加新的圓盤到塔2並丟掉塔3最上方的圓盤。
              true -> Number = hd(Tower3List), [{tower1,Tower1List},{tower2, [Number|Tower2List]},{tower3,tl(Tower3List)}]
            end,
    {Number, Status}. % 返回一個包含當前移動圓盤的值和新的狀態標識的元組。

%% ==================================================================================================================
%% 該函數用於將圓盤從起始塔轉移到目的塔。
%% 完成移動後,更新狀態的表示。
%% ==================================================================================================================
move(T, Start, End)->
    % 得到被移動圓盤的值和新的狀態表示。
    {Number, NewStatus} = tool:check_status(T, Start, End),
    io:format("-------------------------------------------~nMove No.~p disk: ~p -------> ~p ~n", [Number, Start, End]),
    % 打印出新的狀態表示。
    display_towers(NewStatus),
    % 返回新的狀態。
    NewStatus.

%% ==================================================================================================================
%% 該函數使用尾遞歸解決圓盤轉移問題。
%% ==================================================================================================================
solve(1, Init, Aux, Dest, T)-> move(T, Init, Dest); % 最上方的圓盤能夠直接移動。
solve(N, Init, Aux, Dest, T) when N > 1->
    % 將 N-1 個圓盤從初始塔移動到中間塔。
    ResetStatus = solve(N-1, Init, Dest, Aux, T),
    % 再將該圓盤從初始塔移動到目的塔。
    ResetStatusAgain = move(ResetStatus, Init, Dest),
    % 再將剩下的 N-1 個圓盤從中間塔移動到目的塔
    solve(N-1, Aux, Init, Dest, ResetStatusAgain).
相關文章
相關標籤/搜索