erlang 尾递归

来源:互联网 发布:java 输出参数 out 编辑:程序博客网 时间:2024/06/02 15:44

前面的博文中说过递归和尾递归的区别.在ERLANG中,到处存在的都是递归,或者说我们实现的时候优先考虑的应该是尾递归.

那应该如何去写好尾递归呢?

递归,除去List Comprehensions,在ERLANG中唯一存在的循环结构就是递归了.所以在ERLANG中递归是需要重点掌握的概念,

当然,正确写好ERLANG递归程序也不是简单的事情.

这几天大量搜索了一下ERLANG关于递归程序写法,就在这总结一下.

Example One:

[plain] view plaincopy
  1. |  %% @Func Duplicate_Forest  
  2. |  %% @Breif 给定一个参数,复制N次返回一个List  
  3. |  Duplicate_Forest(0,_) ->  
  4. |           [];  
  5. |  Duplicate_Forest(N,Term) when N > 0 ->  
  6. |           [Term|Duplicate_Forest(N-1,Term)].  


上面给的Example One是普通的递归写法,程序算是完成了,但是在ERLANG世界里,这样子的程序可是不合格的.

所以需要将这段代码改成尾递归的形式才算是合格.

Tail Recursion实现:

[plain] view plaincopy
  1. | %% @Func Tail_Duplicate_Forest  
  2. | %% @Breif 给定一个参数,复制N次返回一个List(尾递归实现)  
  3. | Tail_Duplicate_Forest(N,Term) ->  
  4. |           Tail_Duplicate_Forest(N,Term,[]).  
  5.    
  6. | Tail_Duplicate_Forest(0,_,List) ->  
  7. |           List;  
  8. | Tail_Duplicate_Forest(N,Term,List) when N > 0 ->  
  9. |           Tail_Duplicate_Forest(N-1, Term, [Term|List]).  


前一篇博文中说过,尾递归的实现需要借助辅助变量,是对递归的"尾"调用的实现,结果都保存在形参中.

所在在上面的尾递归的实现中,添加了第三个辅助变量"List",形参List一直被用来传递上一次迭代调用的结果,作为下一次迭代调用的形参.

递归需要递归的终止条件.而该片段代码的终止条件是N的值为0,所以只需要无条件返回调用结果,而结果就保存在我们的辅助变量List中.

而我们在程序的参数要求方面是两个:N/Term,所以需要辅助变量初始化为[].

Example Two:

[plain] view plaincopy
  1. | %% @Func Reverse_Forest  
  2. | %% @Breif 倒转List中的Element  
  3. | Reverse_Forest([]) ->  
  4. |           [];  
  5. | Reverse_Forest([H|T]) ->  
  6. |           Reverse_Forest(T)++[H].  


这同样是普通递归实现的一个小函数,函数的功能是实现对List的元素倒置.同样,需要将其改为合格的ERLANG尾递归程序.

Tail Recursion实现:

首先记住我们需要的参数只有一个,但是需要一个辅助变量,所以第一个函数分支是比较好些的.

[plain] view plaincopy
  1. | Tail_Recursion_Forest(List) ->  
  2. |           Tail_Recursion_Forest(List,[]);   

下面需要的是对参数的检查,如果List是[],该怎么处理.

[plain] view plaincopy
  1. | Tail_Recursion_Forest([],Acc) -> Acc;  

最后就应该是尾递归的迭代实现部分了.注意调用的结果是通过形参传递就可以了.而Acc就是辅助变量,所以调用应该通过Acc传递.

 

[plain] view plaincopy
  1. | Tail_Recursion_Forest([H|T],Acc) ->  
  2. |           Tail_Recursion_Forest(T,[H|Acc]).  

 

所以尾递归实现的完整代码如下:

[plain] view plaincopy
  1. | %% @Func Reverse_Forest  
  2. | %% @Breif 倒转List中的Element(尾递归实现)  
  3. | Tail_Recursion_Forest(List) ->  
  4. |           Tail_Recursion_Forest(List,[]).      
  5. | Tail_Recursion_Forest([],Acc) -> Acc;  
  6. | Tail_Recursion_Forest([H|T],Acc) ->  
  7. |           Tail_Recursion_Forest(T,[H|Acc]).  


Example Three:

 

[plain] view plaincopy
  1. | %% @Func Sublist_Forest  
  2. | %% @Breif 返回一个List中的前N个Elements  
  3. | Sublist_Forest(_,0) -> [];  
  4. | Sublist_Forest([],_) -> [];  
  5. | Sublist_Forest([H|T],N) when N > 0 ->  
  6. |           [H|Sublist_Forest(T,N-1)].  


下面是尾递归实现的代码:

Tail Recursion实现:

[plain] view plaincopy
  1. | %% @Func Tail_Sublist_Forest  
  2. | %% @Breif 返回一个List中的前N个Elements(尾递归实现)  
  3. | Tail_Sublist_Forest(List,N) -> Tail_Sublist_Forest(List,N,[]).  
  4. | Tail_Sublist_Forest(_,0,SubList) -> SubList;  
  5. | Tail_Sublist_Forest([],_,SubList) -> SubList;  
  6. | Tail_Sublist_Forest([H|T],N,SubList) ->   
  7. |           Tail_Sublist_Forest(T,N-1,[H|SubList]).  

Tips:

tail recursion as seen here is not making the memory grow because
when the virtual machine sees a function calling itself in a tail position
(the last expression to be evaluated in a function), it eliminates the current
stack frame. This is called tail-call optimisation (TCO) and it is a special case
of a more general optimisation named Last Call Optimisation (LCO).

LCO is done whenever the last expression to be evaluated in a function body
is another function call. When that happens, as with TCO, the Erlang VM avoids
storing the stack frame. As such tail recursion is also possible between multiple
functions. As an example, the chain of functions a() -> b(). b() -> c(). c() -> a().
will effectively create an infinite loop that won't go out of memory as LCO avoids
overflowing the stack. This principle, combined with our use of accumulators
is what makes tail recursion useful.

上面这段小贴士的主要是说明Tail Recursion为什么和普通的Recursion不同,不会造成Stack Flow.

同时也说明了EVM对Tail Recursion的优化.

Erlang , Recursion World !!!! ~~~~ ~,~


 


原创粉丝点击