How to understand and use recursion correctly

How to understand and use recursion correctly

What is recursion?

  1. Recursion is a very widely used algorithm (or programming technique). Recursion is used in the encoding of many data structures and algorithms, such as DFS depth-first search, front-middle-post-order binary tree traversal, and so on.
  2. The way a method or function calls itself is called recursive calling, the calling process is called "passing", and the returning process is called "recursive".

Why use recursion? Advantages and disadvantages of recursion?

  • Advantages: The code is concise and easy to understand. (For example, in the pre/middle/post-order traversal of the tree, the implementation of recursion is obviously simpler than loop.)
  • Disadvantages: time and space consumption is large, there is double calculation, and there is a risk of stack overflow.

Three conditions that need to be met for recursion

A problem can be solved by recursion as long as the following three conditions are met at the same time:

  1. The solution of a problem can be decomposed into solutions of several sub-problems
  2. This problem is the same as the sub-problem after decomposition, except that the data scale is different, the solution idea is exactly the same
  3. Recursive termination condition exists

How to write recursive code?

The key to writing recursive code is to find the rule of how to break down big problems into small problems, and based on this, write a recursive formula, then consider the termination condition, and finally translate the recursion formula and termination condition into code.

  1. First put the recursive termination condition and the recursive formula together
f(1) = 1;
f(2) = 2;
f(n) = f(n-1)+f(n-2)
  1. And then into recursive code
int f(int n) {
    
    
  if (n == 1) return 1;
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}

How to understand recursive code

For recursive code, if you try to figure out the entire recursive and recursive process, you will actually enter a misunderstanding of thinking . In many cases, it is difficult for us to understand. The main reason is that we have created this kind of understanding barrier for ourselves. What is the correct way of thinking?

If a problem A can be decomposed into several sub-problems B, C, and D, you can assume that the sub-problems B, C, and D have been solved, and think about how to solve the problem A on this basis. Moreover, you only need to think about the relationship between problem A and sub-problems B, C, and D. You don’t need to think about sub-problems and sub-sub-sub-problems, sub-sub-problems and sub-sub-sub-problems. Relationship between. Mask out the recursive details, so that it is much easier to understand.

Therefore, as long as we encounter recursion, we abstract it into a recursive formula , instead of thinking about the layer-by-layer call relationship, and don't try to use the human brain to decompose each step of the recursion.

Recursion FAQ

Stack overflow

The function call uses the stack to store 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 the stack will be popped when the function returns. The system stack or virtual machine stack space is generally not large. If the data to be solved recursively is large, the call level is deep, and it is always pushed onto the stack, there will be a risk of stack overflow.

Solution:
You can limit the maximum depth of recursive calls in the code, and directly return an error if it exceeds. Examples of pseudo code are as follows:

// 该例子只适用于一个f分解为一个f
// 全局变量,表示递归的深度。
int depth = 0;

int f(int n) {
    
    
  ++depth;
  if (depth > 1000) throw exception;
  
  if (n == 1) return 1;
  return f(n-1) + 1;
}

But this approach does not completely solve the problem, because the maximum allowable recursion depth is related to the remaining stack space of the current thread and cannot be calculated in advance. If calculated in real time, the code is too complex, which will affect the readability of the code.

Repeated calculation

In order to avoid repeated calculations, we can save the solved f(k) through a data structure (such as a hash table). When the recursive call reaches f(k), first see if it has already been solved. If it is, it will directly retrieve the value from the hash table and return it without repeating calculations.

summary

  • Recursion is a very efficient and concise coding technique. As long as the "three conditions" are met, the problem can be solved by recursive code.
  • However, recursive code is also difficult to write and understand. The key to writing recursive code is not to get around yourself. The correct posture is to write the recursive formula, find the termination condition, and then translate it into recursive code.
  • Although recursive code is concise and efficient, recursive code also has many drawbacks. For example, stack overflow, repeated calculations, function calls are time consuming, and space complexity is high. Therefore, when writing recursive code, these side effects must be controlled.

How to debug recursive code?

  1. Print the log found that the recursive value.
  2. Combine conditional breakpoints for debugging.

Debugging recursion is like writing recursion. Don't be trapped by the details of each step. The key is to confirm whether the recursion relationship and the end condition are correct. Use conditional breakpoints to focus on debugging the first two steps and the final two steps.

Noted on: 2020/03/15

Guess you like

Origin blog.csdn.net/curtis0730/article/details/104879484