Update: Can all recursion be rewritten to be non-recursive?

file

foreword

This article is included in the album: http://dwz.win/HjK , click to unlock more knowledge of data structures and algorithms.

Hello, I'm Brother Tong, a hard-core man who climbs twenty-six floors every day and doesn't forget to read the source code.

In the previous section, we used bitmaps to introduce the implementation of the 12306 ticket grabbing algorithm. Students who have not received the push can click on the album above to view it, or view it in the Princess historical news.

At the end of the previous section, Brother Tong received the latest information, saying that all recursion can be rewritten as non-recursive, is that true? How to achieve it? Is there a trick?

Let us enter today's study with these questions.

What is recursion?

The so-called recursion refers to the behavior of the program calling itself in the process of running.

This kind of behavior can't go on indefinitely, there must be an exit, called 边界条件, so the recursion can be divided into three sections: forward section, reach boundary condition, return section, in these three sections we can do something, such as The forward segment reduces the size of the problem, and the return segment organizes the results.

This may be abstract, so let's look at a simple case:

How to implement the addition of 1 to 100 with recursion?

Everyone will know how to add 1 to 100 using a loop. The code is as follows:

public class Sum {
    public static void main(String[] args) {
        System.out.println(sumCircle(1, 100));
    }

    private static int sumCircle(int min, int max) {
        int sum = 0;
        for (int i = min; i <= max; i++) {
            sum += i;
        }
        return sum;
    }
}

So, how to implement it using recursion?

How to implement recursion quickly?

First, we have to find the boundary conditions for this problem, add 1 to 100, the boundary conditions can be 1 or 100, if we start from 1, then the boundary conditions are 100, and vice versa.

After finding the boundary conditions, it is to reduce the scale of the problem. For this problem, calculate the sum of 1 to 100. Then, can you first calculate the sum of 1 to 99 and then add 100? It is definitely possible, so that the size of the problem is reduced until, the problem size is reduced to 1 to 1 sum.

OK, let's see the code implementation:

private static int sumRecursive(int min, int max) {
    // 边界条件
    if (min >= max) {
        return min;
    }
    // 问题规模缩小
    int sum = sumRecursive(min, max - 1);
    // 加上当前值
    sum += max;
    // 返回
    return sum;
}

Is not it simple? It can also be simpler:

private static int sumRecursive2(int min, int max) {
    return min >= max ? min : sumRecursive2(min, max - 1) + max;
}

686?

Therefore, the most important thing to use recursion is to find the boundary conditions, and then let the scale of the problem shrink toward the boundary conditions until the boundary conditions are reached, and finally return in turn. This is also a routine for quickly implementing recursion.

So, using recursion seems simple enough, but is there any downside to it?

To understand the drawbacks, we must start with the nature of recursion.

What is the nature of recursion?

We know that there is a parameter called when the JVM starts -Xss, it does not indicate an XSS attack, it refers to the size of the thread stack that each thread can use.

So, what is a thread stack?

Everyone understands the stack, and we have also learned it in the previous chapters. Using the stack, you can realize the function of the calculator, which is very convenient.

The thread stack, as the name suggests, refers to the stack used in the thread running process.

So, why do threads use stacks in the process of running?

This has to say the essence of method invocation.

Take a simple example:

private static int a(int num) {
    int a = 1;
    return a + b(num);
}

private static int b(int num) {
    int b = 2;
    return c(num) + b;
}

private static int c(int num) {
    int c = 3;
    return c + num;
}

In this code, method a() calls method b(), and method b() calls method c(). () to return, then save method a() and its state to the stack, and then call method b(). Similarly, when calling method b(), you find that you need to call method c() before returning, then Put the method b() and its state on the stack, and then call the method c(). When calling the method c(), there is no need to call other methods. After the calculation is completed, return, and after the return, the method b() and the method are taken from the top of the stack. In the state at that time, continue to run method b(), method b() finishes running, return, and then remove method a() and the current state from the stack, after the calculation is completed, method a() returns, and the program waits to end.

Still pictured above:

file

Therefore, the essence of method invocation is the use of stack.

Similarly, a recursive call is a method call, so the recursive call is also the use of the stack. However, the stack will become very large. For example, for the addition of 1 to 100, there will be 99 pushes and pops from the stack. operate.

file

So, to sum up, recursion has the following two disadvantages:

  1. The operation is time-consuming because it involves a large number of push and pop operations;
  2. It is possible to overflow the thread stack because recursive calls take up a lot of space on the thread stack.

So, should we stop using recursion?

Of course not. The reason why recursion is used is because it is very simple to use, can solve our problems quickly, and reasonably control the length of the recursive call chain, which is a good recursion.

Since the essence of recursive calls is the use of stacks, can we simulate a stack by ourselves and change recursive calls to non-recursive ones?

of course can.

Modify recursion to non-recursive routine

Still using the above example, now we need to change the recursion to non-recursive, and not the form of using the for loop, how to achieve it?

First, we have to simulate a stack ourselves;

Then, find the boundary conditions;

Finally, reduce the problem size in the direction of boundary conditions;

OK, the above code:

private static int sumNonRecursive(int min, int max) {
        int sum = 0;
        // 声明一个栈
        Stack<Integer> stack = new Stack<Integer>();
        stack.push(max);
        while (!stack.isEmpty()) {
            if (max > min) {
                // 要计算max,先计算max-1
                stack.push(--max);
            } else {
                // 问题规模缩小到一定程度,计算返回
                sum += stack.pop();
            }
        }
        return sum;
    }

Well, is it very simple? In fact, it is the same as the recursive routine, except that it is implemented by simulating the stack by itself.

This example may not be so obvious, let's take another example of binary tree traversal to see.

public class BinaryTree {

    Node root;

    // 插入元素
    void put(int value) {
        if (root == null) {
            root = new Node(value);
        } else {
            Node parent = root;
            while (true) {
                if (value <= parent.value) {
                    if (parent.left == null) {
                        parent.left = new Node(value);
                        return;
                    } else {
                        parent = parent.left;
                    }
                } else {
                    if (parent.right == null) {
                        parent.right = new Node(value);
                        return;
                    } else {
                        parent = parent.right;
                    }
                }

            }
        }
    }

    // 先序遍历
    void preTraversal(Node x) {
        if (x == null) return;
        System.out.print(x.value + ",");
        preTraversal(x.left);
        preTraversal(x.right);
    }

    static class Node {
        int value;
        Node left;
        Node right;

        public Node(int value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        BinaryTree binaryTree = new BinaryTree();
        binaryTree.put(3);
        binaryTree.put(1);
        binaryTree.put(2);
        binaryTree.put(7);
        binaryTree.put(8);
        binaryTree.put(5);
        binaryTree.put(4);
        binaryTree.put(6);
        binaryTree.put(9);
        binaryTree.put(0);

        binaryTree.preTraversal(binaryTree.root);
    }
}

I wrote a binary tree here and implemented its preorder traversal. The binary tree in this test case looks like this:

file

So, the result of the preorder traversal of this binary tree is 3,1,0,2,7,5,4,6,8,9,.

As you can see, using recursive preorder to traverse a binary tree is very simple, and the code is clear and easy to understand, so how can it be modified to a non-recursive implementation?

First, we have to simulate a stack ourselves;

Then, find the boundary condition, which is when the node is equal to empty;

Finally, to reduce the scale of the problem, here the right subtree is pushed onto the stack first, and then the left subtree is pushed onto the stack, because the left is followed by the right;

Well, let's look at the code implementation:

// 先序遍历非递归形式
void nonRecursivePreTraversal(Node x) {
    // 自己模拟一个栈
    Stack<Node> stack = new Stack<Node>();
    stack.push(x);
    while (!stack.isEmpty()) {
        Node tmp = stack.pop();
        // 隐含的边界条件
        if (tmp != null) {
            System.out.print(tmp.value + ",");
            // 缩小问题规模
            stack.push(tmp.right);
            stack.push(tmp.left);
        }
    }
}

After mastering this routine, it is very simple to rewrite recursion to non-recursion. However, the rewritten code is obviously not as clear as recursion.

Well, the recursive rewriting into a non-recursive routine is here, I wonder if you got it? You can also find a recursion and try to rewrite it yourself.

postscript

In this section, we start with the concept of recursion, learn how to implement recursion quickly, and the essence of recursion, and finally, learn the routine of rewriting recursion to non-recursion.

Essentially, this is also the normal usage of the stack as a data structure.

Since we have talked about stacks, is it a bit too much not to talk about queues?

Therefore, in the next section, Brother Tong, who traverses various source codes, will introduce how to implement high-performance queues. Do you want to know the routine? Don't hurry to follow me!

Follow the official account owner "Tong Ge Read Source Code" to unlock more source code, basics, and architecture knowledge.

{{o.name}}
{{m.name}}

Guess you like

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