Algorithm-how to understand recursion and write recursive functions

Not every programmer is born with a deep understanding of recursion. When he was just entering his freshman year, when someone wrote the first recursive function to find the greatest common divisor, he was amazed at how much he could do without loops and the code could be so concise. It is true that recursion is implemented in most cases when the code is very short. Most people also know recursion and can basically understand recursion, but they often don’t know how to write, or the recursion written is often in an endless loop. Writing algorithms is often learned. It is a routine. Only a few people create algorithms. Most people use algorithms. Recursion does have a routine to follow.

This article starts from the recursive Zama step, from a few simple examples to general routines, and disassembles the recursion step by step.

1 The three elements of recursion

Writing recursion means writing the realization of the three elements. The three elements are function, boundary, and recursion formula. Just remember to write this at the beginning, and after writing a few algorithms, you can slowly understand why you want to do this.

1.1 Recursive primary element-function

Clarify what your function is for, what the input parameters of the function should be, what is the return value, these three questions, start with what the function is for, you can define a function f()assuming that each step of the recursive implementation has been implemented , And then to clarify what the implementation does, at least what is required to enter the parameters, the return value and parameter return can be understood as one thing, both for returning to the upper call or a global data, think about the three elements of the function, Then your function is defined.

1.2 Recursion boundary, out of recursion

Similarly, do this first, and then think about why. In this step, the input parameter of the function is judged null. If the parameter is input, the input parameter is the initial value, such as the first 1 or 2 digits of the Fibonacci sequence. You don’t have to think completely, that’s okay, the next step will continue to be improved, so I’ll give the example here is the first 1 or 2 of Fibonacci , instead of directly talking about the conclusion, this step is in the realization of the function , So the way of thinking is to assume that the critical value, initial value, or special value is entered. You have to judge. The first time you write, such as Fibonacci, you can write directly like this

if (n == 1)
  return 1;
if (n == 2)
  return 1; 

What you think is not necessarily right, or it is so elegant, it doesn’t matter, as long as you think about the boundaries. The following is what is meant by the boundary? There are two, one, outliers border, and the other end recursion judgment, than such a problem in n <0 how to do, and n == 1, and n == 2you said earlier, respectively, of course, these two may be considered less completely, assuming you only Considering that in the previous code, or when writing the boundary, it is found that there is too much writing or redundancy, so that it does not affect the result of the program, then after writing the recursion formula, let's review the boundary problem.

1.3 Recurrence formula

This is to talk about the meaning first, and then talk about the realization. The meaning is to gradually reduce the scale of the algorithm, or define a way to make the input value as close as possible to the critical value, that is, to find a relationship between f(n)the f(n-x)sequence and the relationship, which f(n)represents the solution to be solved. The problem scale, f(n-x)the function value of the problem scale smaller than n, is a key step in the recursive function. Without a recursive formula, there is no recursion. Such as the Fibonacci sequence, the recursive formula that f(n)=f(n-1) + f(n-2)we observe this formula, found its first nin n-1and n-2have a relationship, so we look at, if you enter any integer, it n-1,n-2may be negative values, 0, , 1+you can see the border 0And negative numbers are not taken into account, so now, looking back at the recursion of 1.2 above, let's add the boundary and get:

if (n <= 2)
  return 1;

2 Recursive case

Let’s use three simple examples to practice recursion. They are the frog jumping problem, which is equivalent to the Fibonacci sequence, recursively reverse the singly linked list, recursively traverse the tree, and target three

2.1 The problem of frog jumping

The first is to define the function. It is clear that the function has only one unique input value n. The second is to find the recursive end condition or the recursive boundary. It can be found that the easiest to get when the step is 1 or 2. As for the recursive type, you can find that the frog jumps every time At that time, there are two jumping methods. How do the frogs reach the nfirst step? There are two jumping methods, corresponding to f(n-1)and respectively f(n-2), so the recursive formula is f(n)=f(n-1)+f(n-2), then the whole algorithm is as follows:

//一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
//1。定义函数
public int f2(int n) {
   //2.确定边界
    if (n == 1)
        return 1;
    if (n == 2)
        return 2;
   //3.确定递归式
    return f2(n-1) + f2(n-2);
}

Continue to check the boundary and find that if n is less than 1, it will fall into an endless loop, then the program can be changed to this:

if (n == 1)
  return 1;

if (n == 2)
  return 2;

if (n < 1)
  return 0;

//当然简单写,可以这样搞

if (n < 1)
  return 0;
if (n <= 2)
  return n; 

2.2 Recursively reverse singly linked lists

The reversal of a singly linked list is generally considered to be double pointer reversal. Of course, recursive writing is also possible. Similarly, first define the function and find that the function has only one input parameter, the node. nodeThis nodeis applicable to the root node or any intermediate node, and the second determines the boundary , When reversing the singly linked list, node.nextthe boundary may be missed . At this time, there are two ways, 1, redundant writing, as long as you consider it, this may be the boundary, you can never go wrong if you write more, and even you can It’s okay to write two or three more steps. 2. If you write less, just write it recursively and check again, such as reversing the singly linked list. You will see that if it node.nextis empty, then node.next.nextit will report a null pointer problem. After writing the recursive expression, it is best to go back and check the boundary, you can check the gaps, remove redundancy, or add conditions.

The core point of this question is the recursive formula of unraveling the chain, which is

Node last = f3(node.next); //假设当前节点的下一节点后的链表已经反转
node.next.next = node; //当前节点指向的节点指向当前节点
node.next = null ;//原来当前节点是指向下一节点的,解开当前节点,并把节点指向空
//此处解释,为什么指向空,首先可以将node节点理解为第一个节点,那么第一节点反转后,就是最后一个节点,则指向是null,否则它还是指向2,就有问题哟
//那么如果不是第一个节点呢?这个指针是怎么指向的 

For example, suppose that the singly linked list is 1,2,3,4,5so recursive as shown below:

file

Looking at the picture, you can find that the current node of each step is the last one after it is put into the reversal linked list. Then it must point nullto. Understand it!

class Node{
    int data;
    Node next;
} 
public Node f3(Node node) {
    //2.确定返回边界
    if (node == null || node.next == null)
        return node;
    //3.拿到递归推导
    Node last = f3(node.next);
    node.next.next = node;
    node.next = null ;//这个的作用是什么?,解开死循环,最后是有A->B,B->A
    return last;
}

2.3 Recursively traverse the tree

Recursive traversal of the tree is also the simplest. Assuming that you have not seen the traversal code before, consider this problem from scratch. First define the function and confirm that the input parameters are similar to the singly linked list reversal. Only one TreeNodenode is needed , and then consider the boundary as null, And not for null, did you think of it first?

if (node == null)
  return ;

if (node.left == null && node.right == null) {
  System.out.pritln(node.val);
  return ;
}

It seems to be a bit redundant now, but suppose you don’t know, then next recursive, take preorder as an example

//首先节点本身
System.out.println(node.val);      
//然后节点左
preOrder(node.left);      
//然后节点右 
preOrder(node.right); 

That's it. Then review the previous boundary problem. There are only two lines of code above. You can see that when the node is null, it is straightforward return, without considering the child nodes. The boundary of the byte point has been considered in the boundary of the parent node. , Of course, writing this boundary does not affect the operation of the program at all, so the final traversal code is as follows:

 //二叉树前序遍历
public static void preOrder(TreeNode node) {
    if (node == null)
        return;
    System.out.println(node.val);
    preOrder(node.left);
    preOrder(node.right);
}

//二叉树中序遍历
public static void inOrder(TreeNode node) {
    if (node == null)
        return;
    preOrder(node.left);
    System.out.println(node.val);
    preOrder(node.right);
}

//二叉树后序遍历
public static void postOrder(TreeNode node) {
    if (node == null)
        return;
    preOrder(node.left);
    preOrder(node.right);
    System.out.println(node.val);
}

2.4 Construct a binary tree through a sequence

Next, we fill in a recursive algorithm problem, enter a binary tree sequence to restore the construction of the binary tree, and test the previous tree traversal code by the way. After we are also familiar with the recursive routine, we will write the code directly

//1.定义函数确认,只需要一个参数,即剩余序列
public static TreeNode createBinaryTree(LinkedList<Integer> inputList) {
        //定义一个空的树节点,此处为了整齐划一,在边界和递归体里面都可以用,所以写在第一行
        TreeNode node = null;
        //2.边界
        if (inputList == null || inputList.isEmpty())
            return node;

        //3.主要递归体,从链表中删除并取第一个元素,构建好左右节点,最后返回当前节点
        Integer data = inputList.removeFirst();
        //data,主要是异常值判断,前面已经判断过链表为空了
        if (data != null) {
            node = new TreeNode(data);
            node.left = createBinaryTree(inputList);
            node.right = createBinaryTree(inputList);
        }
        return node;
}

    public static void main(String[] args) {
        //前序遍历序列
        LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4}));

        TreeNode node = createBinaryTree(inputList);

        //前序遍历
        System.out.println("前序遍历:");
        preOrder(node);

    } 

3. Summary

How to write recursion is three steps. First, confirm the input value and return value of the function, that is, what the function should do. Secondly, to judge the boundary, write down all imaginable boundaries. Finally, write the recursive body, including the return value of the function, and then go back to check the boundary, add, delete, and modify the boundary.

ps: In more cases, I just didn't think about the algorithm. If I think about it, I can use the simulation method to draw the entire picture, and write the code by referring to this article. You can do it in one go. . . Wu Xie, Xiao San Ye, a little rookie in the background, big data, and artificial intelligence. Please pay attention to morefile

Guess you like

Origin blog.csdn.net/hu_lichao/article/details/109966601