Python core notes for test development (13): recursive functions

13.1 Concept

  • Inside a function, other functions can be called. A function is recursive if it internally calls itself.
  • In theory, all recursive functions can be written as loops, but the logic of loops is not as clear as recursion.

Calculate the factorial n! = 1 x 2 x 3 x … xn, represented by the function fact(n):

def fact(n):
  if n==1:
      return 1
  return n * fact(n - 1)

13.2 Routines for writing recursive code

The key to writing recursive code is to find the rules of how to decompose big problems into small problems, and then follow the following routines to achieve:

  • The first step is to write the recurrence formula

Taking factorial calculation as an example, the recursive formula is: fact ( n )= n != ( n −1)×⋅⋅⋅3×2×1= n ×( n −1)!= n × fact ( n − 1)

  • The second step is to consider the termination conditions

Taking the calculation of factorial as an example, when the termination condition is n=1, fact(1)=1.

13.2.1 The Fibonacci sequence

Let's take another example of the Fibonacci sequence, where the last element of the Fibonacci sequence is the sum of the first two adjacent elements. for example:

0,1,1,2,3,5,8,13,21,34,55,…。

So how do we get what the nth number is? Take two steps:

The first step is to write the recursion formula. To find the nth element, you can first find the values ​​of n-1 and n-2 elements, and then sum these two, so the formula is:

Fibonacci (n) = Fibonacci (n - 1) + Fibonacci (n - 2)

The second step is to consider the final termination condition. There are three termination conditions: when n=0, f(n)=0; when n=1, f(n)=1; when n=2, f(n)=1.

if n < 1:  # 递归终止条件
   return 0
if n in [1, 2]:  # 递归终止条件
   return 1 

Converted to complete code is:

def fibonacci(n):
    if n < 1:  # 递归终止条件
        return 0
    if n in [1, 2]:  # 递归终止条件
        return 1 
    return fibonacci(n - 1) + fibonacci(n - 2)  # 递归公式

Whether writing recursion or reading recursive code, as long as recursion is encountered, we abstract it into a recursive formula, without thinking about the calling relationship layer by layer, and do not try to use the human brain to figure out how each step of the computer is executed.

13.2.2 How many ways are there for n steps

Let's look at an example. If there are n steps, you can cross one step or two steps at a time. How many ways are there to walk these n steps?

Let's start from the first step. If the first step is 1 step, the problem becomes how many ways are there for n-1 benches. If the first step is 2 steps, the question becomes how many ways are there for n-2 steps. We sum the moves of n-1 steps and the moves of n-2 steps, which is the move of n steps. The formula is f(n)=f(n-1)+f(n-2). This is the recursive formula.

Let's take a look at the termination condition. The last step does not need to continue recursion. There is only one way to go, that is, f(1)=1. Let's put this into the recursive formula to see if we can find f(2) through this termination condition, and find that f(2)=f(1)+f(0), that is, we only know that f(1) can't Find f(2), so either know the value of f(0), or directly use f(2) as a recursive termination condition. f(0) means that there are several ways to move 0 steps, and f(2) means that there are several ways to move 2 steps. Obviously, f(2) is easier to understand. Therefore, setting f(2)=2 is also a termination condition, indicating that there are two ways to move the last two steps, that is, one step at a time and two steps at a time. With f(1) and f(2), we can find f(3) and then f(n).

Converted to code is:

def walk(n):
    if n == 1:  # 递归终止条件
        return 1
    if n == 2:  # 递归终止条件
        return 2
    return walk(n - 1) + walk(n - 2)  # 递归公式

13.3 What kinds of problems can recursion solve?

  • The solution to the original problem can be decomposed into solutions to several subproblems
  • The original problem and the sub-problem, only the data scale is different, the solution idea is exactly the same
  • There is a recursive termination condition

13.4 Problems with recursion

  • stack overflow
  • Repeated calculation

When writing recursive code, we will encounter many problems, one of the more common is stack overflow, and stack overflow will cause systemic crash, and the consequences will be very serious. What is stack overflow?

Function calls use the stack to hold temporary variables. Every time a function is called, the temporary variable will be encapsulated as a stack frame and pushed into the memory stack, and when the function execution completes and returns, it will be popped out of the stack. The system stack or virtual machine stack space is generally not large. If the data to be solved recursively is very large, the call level is deep, and it is pushed onto the stack all the time, there is a risk of stack overflow.

You can view the call stack through the Pycharm tool. Add a breakpoint to the line of code in the recursive formula, and continuously execute Step Over. You can see that the stack information in the Frames window will continue to increase and decrease. When the function is called once, it will increase by one frame, and when the call returns, it will decrease by one frame. Finally, return to the first layer stack func.py. The risk of stack overflow mentioned above is reflected in too many stack frames in the Frames window.

So, how to avoid stack overflow?

This problem can usually be solved by limiting the maximum depth of recursive calls in the code. For example, the Python language limits the recursion depth. When the recursion depth is too high, a
RecursionError: maximum recursion depth exceeded in comparison exception will be thrown to prevent systemic crashes.

We can also set the recursion depth ourselves in the code, for example, limit n to no more than 100, the code is as follows:

def walk(n):
    if n == 1:
        return 1
    if n == 2:
        return 2
    if n > 100:
        raise RecursionError("recursion depth exceede 100")
    return walk(n - 1) + walk(n - 2)

In addition to this, there is the problem of double computation when using recursion. What's the meaning? Take the example of the steps away to illustrate.
For example, to calculate the walking method f(6) of 6 steps, the process is as follows:

insert image description here

From the figure, we can intuitively see that to calculate f(5), we need to calculate f(4) and f(3) first, and to calculate f(4), we also need to calculate f(3). Therefore, f( 3) is calculated many times, which is the double-counting problem.

So how to solve this problem? In order to avoid double calculation, we can save the solved f(k) through a dictionary. When the recursive call to f(k), first see if it has been solved. If it is, the value is taken directly from the dictionary, and there is no need to repeat the calculation, so that the problem just mentioned can be avoided.

Modify the code for calculating the steps to solve the problem of repeated calculation:

data = dict()  # 保存中间结果


def walk(n):
    if n == 1:
        return 1
    if n == 2:
        return 2
    if n > 100:
        raise RecursionError("recursion depth exceed 100")
    if n in data:  # 如果在中间结果中,则直接返回,不用进入递推公式再次计算
        return data[n]
    result = walk(n - 1) + walk(n - 2)  # 在递归公式前面增加个查找步骤
    data[n] = result  # 将计算结果保存在中间结果data字典中
    return result


print(walk(6))

Guess you like

Origin blog.csdn.net/liuchunming033/article/details/107896189