Understanding recursion and tail recursion in one sentence
Recursion is the call of ordinary culverts, which consumes resources
Tail recursion is the last call to itself, and there is no need to process data in the middle, so there is very little resource consumption.
This is like the benefit of iterators.
Programming is complex and programming is simple. Simple logic can be transformed into complex programs or systems through code organization. When I was studying physics in the past, the teacher said that the process of the physics questions in the exam is quite complicated (there is no need to take the simple exam). There are many ways to solve the problem, and the decomposition method is an effective way. Complex processes are broken down into simple theorems. Just like screws, tires, and glass are simple, but can be combined to form a complex car.
Programming is similar, and the core philosophy is even ridiculously simple, one is 指针
, the other is 递归
. For those who deeply understand the two concepts, many complex systems or designs will be simplified and clear at a glance.
recursion
Recursion, where a function calls itself internally, is recursion. Recursion is also very common in life, such as our eyes, you look at each other's eyes, there is you in the other's eyes, and you have her in that, infinite loop. For another example, when you hold a mirror and look at another mirror, you will find a mirror with your finger in the mirror, and so on.
tail recursion
Functions can call themselves recursively, or they can call other functions at the end. If the last action in a function is the case of a function call: that is, the case where the return value of the call is directly returned by the current function. Such a call is 尾调用
. If it is tail calling itself, that is 尾递归
.
Tail recursion is a form that expresses concepts that can be optimized by some compilers. The special form of tail recursion determines that this recursive code can be executed without backtracking (usually recursion requires Backtracking). If the compiler makes this optimization for the tail recursive form of recursive code, it is possible to complete the execution process that originally required linear complexity stack memory space with constant complexity space.
尾递归
Typically used to implement the following repeated computations. The general language does not support 尾递归
it, that is, it is not optimized. For example java
, python
. They use loop iterations to achieve the same effect.
factorial calculation
The most commonly used example to explain recursion is the 阶乘
algorithm. The following uses Python
, Elixir
, Scheme
respectively, to implement the commonly used recursive algorithm.
class Factorial(object):
@classmethod
def recursion(cls, n):
if n == 1:
return 1
return n * cls.recursion(n - 1)
Factorial.recursion(5) # 输出 120
The magic book (SICP) simply demonstrates this calling process:
recursion(5)
5 * recursion(4)
5 * (4 * recursion(3))
5 * (4 * (3 * recursion(2)))
5 * (4 * (3 * (2 * recursion(1))))
5 * (4 * (3 * (2 * 1)))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120
After the function is called, it will continue to call itself and accumulate memory on the stack. The solution of scheme is also very simple:
#lang scheme
(define (recursion n)
(if (= n 1)
1
(* n (recursion (- n 1)))))
(recursion 5) ; 输出 120
Again, elixir is easy to implement:
defmodule Factorial do
def recursion(n) when n == 1 do
1
end
def recursion(n) do
n * recursion(n-1)
end
end
IO.puts Factorial.recursion(5) # 输出 120
The above is a recursive call, not tail recursion. If tail recursion is used, the python code is as follows:
class Factorial(object):
@classmethod
def tail_recursion(cls, n, acc=1):
if n == 1:
return acc
return cls.tail_recursion(n - 1, n * acc)
Factorial.tail_recursion(5)
The calling process of tail recursion is roughly
tail_recursion(5, 1)
tail_recursion(4, 20)
tail_recursion(3, 60)
tail_recursion(2, 120)
tail_recursion(1, 120)
120
The compiler will optimize according to the tail recursion method, so that the recursive call will not accumulate memory like linear recursion. Just like the effect of loop iteration. This is also the problem with functional programming languages dealing with iteration.
Tail recursion optimization is mainly the optimization of stack memory space, which is O(n) to O(1); as for the optimization of time, it is actually caused by the reduction of memory allocation work caused by the optimization of space, which is a Constant optimization will not bring qualitative changes.
Then look at the implementation of the scheme
(define (tail-recursion n acc)
(if (= n 1)
acc
(tail-recursion (- n 1) (* n acc))))
(tail-recursion 5 1)
After reading two examples, tail recursion is still well understood. The form on the disk is the last return, whether it simply returns a function call or returns a function calculation. which is
- Tail recursive return returns
return cls.tail_recursion(n - 1, n * acc)
only pure functions - Ordinary recursive return
return n * cls.recursion(n - 1)
Return function and other expression operations
Functional languages basically support tail recursion for iterative functions. The following is an example of elixir
defmodule Factorial do
def tail_recursion(n, acc) when n == 1 do
acc
end
def tail_recursion(n, acc \\ 1) do
tail_recursion(n-1, n * acc)
end
end
IO.puts Factorial.tail_recursion(5)
Iteration and recursion
Functional programming languages, usually without what other languages call the loop keyword. When iteration is required, recursion can be used. At first it was also difficult to understand how recursion was implemented? In fact, when processing the loop, the loop conditions are controlled by the loop factor, and the processing calculation is performed in the loop body. Recursion can also do this, the condition of the recursive condition to terminate can be set with the recursion parameter.
The following demonstration gives a list, iterates over the elements of each list, and doubles the value of each element. The same is represented in three languages:
class Double(object):
@classmethod
def recursion(cls, lst):
if not lst:
return []
else:
head, tail = lst.pop(0), lst
return [2 * head] + cls.recursion(lst)
@classmethod
def tail_recursion(cls, lst, new_lst=[]):
if not lst:
return new_lst
else:
head, tail = lst.pop(0), lst
new_lst.append(2 * head)
return cls.tail_recursion(tail, new_lst)
Double.recursion([1, 2, 3, 4, 5])
Double.tail_recursion([1, 2, 3, 4, 5])
Scheme
(define (recursion lst)
(if (null? lst)
`()
(cons (* 2 (car lst)) (recursion (cdr lst)))))
(define (tail-recursion lst new-lst)
(if (null? lst)
new-lst
(tail-recursion (cdr lst) (append new-lst (list (* 2 (car lst)))))))
(recursion (list 1 2 3 4 5))
(tail-recursion (list 1 2 3 4 5) `())
Elixir
defmodule Double do
def recurssion([head|tail]) do
[2 * head | recurssion(tail)]
end
def recurssion([]) do
[]
end
def tail_recursion([head|tail], new_list) do
new_list = new_list ++ [2 * head]
tail_recursion(tail, new_list)
end
def tail_recursion([], new_list) do
new_list
end
end
Double.recurssion([1, 2, 3, 4, 5])
Double.tail_recursion([1, 2, 3, 4, 5], [])
After learning about recursion and tail recursion, another thing to understand is that not every language supports tail recursion. The above is python
not supported. Python uses tail recursion, which overflows when the amount of data is slightly larger. Also, languages like Scheme and Elixir are well supported. When you need to write logic during traversal, you can abstract the logic into functions, which is more conducive to code modularization and reuse.
To sum up, the ordinary recursive process is a function call, which involves the return address, function parameters, register values, etc., and pushes the stack, etc., and the tail recursion stack operation is not necessary, and there will be no intermediate results that need to be cached. Usually whether it is supported at the language level, optimized in the compiler or interpreter.
Author: Human World
Link : http://www.jianshu.com/p/1f69cb4525ec
Source: Jianshu
Copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.