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).
相关文章
相关标签/搜索