Recursion and tail recursion

       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.

12903486_211n.jpg
12903486_211n.jpg

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, Schemerespectively, 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 pythonnot 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. 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326923639&siteId=291194637