Recursive Best Practices

Many people in the freshman year, it has been in contact with recursion, but I'm sure a lot of people when beginners just getting started with recursion, is forced to look ignorant, I did too, I feel that, recursive Amazing!

There may also be a recursive most people know, but also to see to understand recursion, but in the actual process of doing problems, but do not know how to use, sometimes prone to being recursively to get confused. There are also several people asked me if I had to grasp recursion shortcut ah. To be honest, where so many shortcuts ah, but I still wanted to write an article, talk about some of my experiences, perhaps, be able to bring some help to you.

In order to take into account a beginner, I would start from the most simple question!

Three elements recursive

The first elements: clear this function you want to do

For recursion, I think a very important thing is that the function of this function is what he wants to complete what is one thing, but this is entirely up to you to define. That is, we function inside the code first no matter what, but must first understand that you want to use this function is doing.

For example, I define a function

1 // 算 n 的阶乘(假设n不为0)
2 int f(int n){
3
4 }

Operators of this function is the factorial of n. Well, we have defined a function, and defines what its function is, then we look at the second element.

The second element: Looking recursive end condition

The so-called recursive, that will function inside the code, call the function itself, so we have to find the end of the recursion conditions , otherwise, would have been to call themselves into the bottomless pit. In other words, we need to find out when the argument why, recursion end, directly after the results returned , please note that at this time we must be based on the value of this parameter can directly know what the result of the function yes.

For example, the above example, when n = 1, then you should be able to directly know f (n) What is it? In this case, f (1) = 1. Improve our internal function code, the code inside a second element added, as follows

1 // 算 n 的阶乘(假设n不为0)
2 int f(int n){
3    if(n == 1){
4        return 1;
5    }
6  }

Some people might say, when n = 2, then we can know how much f (n) is equal to, ah, that I can put n = 2 end conditions as recursive it?

Of course, as long as the parameters of what you think, you can directly know the result of the function, then you can use that argument as a condition of ending, so the following code is also possible.

1 // 算 n 的阶乘(假设n>=2)
2  int f(int n){
3    if(n == 2){
4        return 2;
5    }
6  }

Note that I write the code inside the comment, assuming n> = 2, because if n = 1, and will be missed when n <= 2, f (n) = n, so in order to be more precise, we can write this:

1 // 算 n 的阶乘(假设n不为0)
2 int f(int n){
3    if(n <= 2){
4        return n;
5    }
6  }

The third element: to find equivalence relation function

The third element is that we want to continue to narrow the range of parameters , after narrowing, we can pass some auxiliary variables or operations, the results of the original function unchanged.

For example, f (n) of this range is relatively large, we can make f (n) = n * f (n-1). Thus, the scope becomes a n a n-1, the smaller the range, and for the original function f (n) remains unchanged, we need to let the f (n-1) is multiplied by n.

Plainly, is to find an equivalence relation original function, f (n) equivalent relationship is n * f (n-1), i.e.,

f(n) = n * f(n-1)。

This equivalence relation is looking for, it can be said is the most difficult step, and if you do not quite understand it does not matter, because you're not a genius, you also need to have more contact with a few questions, I'll be in the next article to find 10 Road recursive questions so that you slowly become familiar .

Identify this equivalence, continue to improve our code, we put this formula written into the equivalent function in. as follows:

1  // 算 n 的阶乘(假设n不为0)
2  int f(int n){ 
3    if(n <= 2){
4        return n;
5    }
6  // 把 f(n) 的等价操作写进去
7   return f(n-1) * n;
8 }

So far, the recursive three elements are already written into the code, so the internal code f (n) function we've written.

This is the most important of the three elements of recursive, recursive every time when you force yourself to try to find these three elements.

Still do not understand? It does not matter, I'll talk about some issues in accordance with this model.

Some may feel a little foundation I wrote too simple, no patience to see? Shaoxia, please continue to watch, I will talk about the following how to optimize recursion . Of course, please feel free to chiefs, can directly pull the bottom of the message give me some advice, very grateful!

Case 1: Fibonacci number

Fibonacci number is such a number of columns: 1,1,2,3,5,8,13,21,34 ..., i.e., a first term f (1) = 1, the second term f (2) =. 1 ......, n th item is f (n) = f (n-1) + f (n-2). Find the value of n is the number of items.

1, first recursive function-

Suppose f (n) is a function of the evaluation of the first n items, as follows:

1 int f(int n){
2
3 }

2, find the end of the recursion conditions

Obviously, when n = 1 or n = 2, we can easily know the result f (1) = f (2) = 1. Therefore, the end condition may be a recursive n <= 2. code show as below:

1 int f(int n){
2    if(n <= 2){
3        return 1;
4    }
5 }

The third element: to find equivalence relation function

Title has the equivalence relation to us, so we can know easily f (n) = f (n-1) + f (n-2). I said, equivalence relation is one of the most difficult to find, and that subject confused relationship to us, and this is too easy, well, this is for my readers to take into account the almost zero-based.

Therefore, final code as follows:

1	 int f(int n){ 
2    // 1.先写递归结束条件
3    if(n <= 2){
4        return n;
5    }
6    // 2.接着写等价关系式
7    return f(n-1) + f(n - 2);
8 }

Get, is not it simple?

Zero-based or may not understand, it does not matter, then slowly follow this practice mode! Well, there may be big brothers in Tucao too simple.

Case 2: Xiaoqing leapfrog stairs

A frog can jump on a Class 1 level, you can also hop on level 2. The frog jumped seeking a total of n grade level how many jumps.

1, first recursive function-

Suppose f (n) is a function of n-th stage seeking a frog jump step jumps total number of species, as follows:

1 int f(int n){ 
2
3 }

2, find the end of the recursion conditions

I said, seeking recursive end condition, you directly to the n compressed into very small on the line, because the smaller the n, we calculated how much easier it is intuitive f (n), so that when n = 1, you know f (1) how much is it? Intuitive enough, right? I.e., f (1) = 1. code show as below:

1 int f(int n){
2    if(n == 1){
3        return 1;
4    }
5 }

The third element: to find equivalence relation function

Every jump, the little frog can jump to a higher level, you can jump two steps, that is, every time you jump, there are two small frog jumps.

The first jump method: The first time I jumped a step, then left the n-1 level has not jumped, there are f (n-1) types of the n-1 remaining steps of jumps.

The second jumps: first jumped two steps, then the remaining n-2 Ge steps yet, the remaining n-2 Ge step jumps have f (n-2) species.

Therefore, all of the little frog jumps both jumps is the sum, i.e., f (n) = f (n-1) + f (n-2). So far, equivalence relation on seeking out. So write code:

1 int f(int n){
2    if(n == 1){ 
3        return 1;
4    }
5    ruturn f(n-1) + f(n-2);
6 }

We feel that the above code, right?

A is of little, when n = 2, there will obviously f (2) = f (1 ) + f (0). We know, f (0) = 0, is the end of the recursive Logically, not continue down the call, but the above code logic we will continue to call f (0) = f (-1 ) + f (-2) . This can lead to unlimited calls, enter an infinite loop .

This is what I want to and you say, about the recursive end condition is serious enough problem , there are a lot of people in the use of recursion, due to the termination condition is not stringent enough, resulting in an infinite loop occurs. That is, when we found out in the second step, when a recursive end condition, and end condition can be written into the code, and then the third step, but please note that , after the third step we find the equivalent function, had to go back a second step, according to the third step function call relationships, it will be some end condition missing. Like the above, f (n-2) This function is called, there may be cases f (0) appeared, resulting in an infinite loop, so we put it back on. code show as below:

1	 int f(int n){
2    //f(0) = 0,f(1) = 1,等价于 n<=2时,f(n) = n。
3    if(n <= 2){
4        return n;
5    }
6    ruturn f(n-1) + f(n-2);
7 }

Some might say, I do not know my end conditions have not missed how to do? Do not worry, a few more practice to know how to do.

See here someone might want to Tucao, these two questions are too easy, right? ? You can not be so perfunctory. Shaoxia, do not go ah, here's a little difficult debut.

Here also is not difficult, and it is harder than the above topic just a little bit, especially in the third step of finding equivalent.

Case 3: Reverse a singly linked list.

Reverse a singly linked list. For example the list is: 1-> 2-> 3-> 4. After inversion is 4-> 3-> 2-> 1

List of nodes defined as follows:

1 class Node{
2    int date;
3    Node next;
4 }

Although the Java language, but even if you never learned Java, I think it is little effect, I can understand.

Or the old routine, the three elements of step by step.

1, the recursive function-defined

Suppose function reverseList (head) is the inverse function of the list, but wherein the head node represented by the list head. code show as below:

1 Node reverseList(Node head){
2
3 }

2. Find the termination condition

When the list only one node, or if the table is empty, then you should know the results of it? Han also do not dry direct, head directly to the return chant. code show as below:

1 Node reverseList(Node head){
2    if(head == null || head.next == null){
3        return head;
4    }
5 }

3. Look for equivalence relations

This equivalence relation n is a value not as easy to find. But I tell you, it's equivalent conditions, the range must be constantly shrinking, for the list, the number of nodes linked list is constantly getting smaller, so if you can not find it, you first of reverseList ( head.next) recursively go again to see the result is Zeyang. List node as e.g.

img

We narrowed down on the first try 2-> 3-> 4 recursive, i.e., the code follows

1 Node reverseList(Node head){
2    if(head == null || head.next == null){
3        return head;
4    }
5    // 我们先把递归的结果保存起来,先不返回,因为我们还不清楚这样递归是对还是错。,
6    Node newList = reverseList(head.next);
7 }

In the first step of our time, it has been defined reverseLis t function can be a function of a single chain reversal, so we 2-> 3-> 4 results after inversion should be so:

img

We 2-> 3-> 4 recursively to 4-> 3-> 2. However, the node 1 We did not touch it, so the next node 1 still connected the two.

what's next? What should I do?

In fact, the next is simple, we then need only the next node 2 points to 1, then the next 1 point to null, not on the line? , I.e., after the change by the results newList following list:

img

That is, reverseList (head) equivalent ** reverseList (head.next) ** + a change point of the two nodes 1 and 2 . Well, find out the equivalence relation, the code is as follows (detailed explanation):

 1 //用递归的方法反转链表
 2 public static Node reverseList2(Node head){
 3    // 1.递归结束条件
 4    if (head == null || head.next == null) {
 5             return head;
 6         }
 7         // 递归反转 子链表
 8         Node newList = reverseList2(head.next);
 9         // 改变 1,2节点的指向。
10         // 通过 head.next获取节点2
11         Node t1  = head.next;
12         // 让 2 的 next 指向 2
13         t1.next = head;
14         // 1 的 next 指向 null.
15        head.next = null;
16        // 把调整之后的链表返回。
17        return newList;
18    }

This question is the third step look very ignorant? Normal, because you do too little, may not have thought so also, a few more practice on it. However, I hope that through these three questions, give some idea of ​​when you do after the problem with recursion, you can do question the future as I think this model. It is impossible to grasp an article by recursive, but also much more practice, I believe that you seriously look at my article, read it several times, will be able to find some ideas! !

I have stressed many times, a few more practice, so, looking back I would probably 10 recursive exercises for them to learn, but I am looking for might be difficult. Not so, simple as it is today, so, beginners have to find yourself a problem and thought, believe me, mastered recursion, your abstract thinking ability will be stronger!

Next, I talk about some optimizations recursion.

Some optimization thinking about recursion

1. Consider whether to repeat calculation

Tell you what, if you use recursion when not optimized, there is a very very very large number of sub-problems to be double counting.

What is a child problem? f (n-1), f (n-2) .... it is f (n) of the sub-problems.

For example, Case 2 that question, f (n) = f (n-1) + f (n-2). FIG recursive call state as follows:

img

Not seen, when the recursive computation, the calculation is repeated twice f (5), five f (4). . . . This is very scary, n is greater, the more double counting, so we must be optimized.

How to optimize? Generally, we can put the results of our calculations guarantee it, for example, the f (4) The calculation results guarantee it, when you want to calculate again f (4), we first determine what, before whether calculated that if calculated directly to f (4) can be taken out of the result, it had not been calculated, then the recursive computation.

What saved it? Arrays can be used to save or HashMap, we used the array to hold, as the n subscript our array, F (n) as the value, e.g. arr [n] = f (n). f (n) are not calculated in the time, we let arr [n] is equal to a specific value, e.g. arr [n] = -1.

When we have to determine, if arr [n] = -1, then the proof f (n) is not calculated, otherwise, f (n) has been calculated over, and f (n) = arr [n]. Directly to the value taken out on the line. code show as below:

 1 // 我们实现假定 arr 数组已经初始化好的了。
 2 	int f(int n){
 3    if(n <= 1){
 4        return n;
 5    }
 6    //先判断有没计算过
 7    if(arr[n] != -1){
 8        //计算过,直接返回
 9        return arr[n];
10    }else{
11        // 没有计算过,递归计算,并且把结果保存到 arr数组里
12        arr[n] = f(n-1) + f(n-1);
13        reutrn arr[n];
14    }
15 }

In other words, the use of recursion when necessary
need to consider whether there are double counting, if double counting, the saved state must take calculated.

2. Consider whether you can from the bottom up

For recursive problem, we are generally from the recursive down until recursively to the bottom, then a layer of a layer with the value returned.

However, sometimes when n is relatively large, for example, when n = 10000 time, you must recursively down to 10000 level until n <= 1 only slowly return the results, if n is too large, it may be enough stack space .

In this case, in fact, we can consider a bottom-up approach. For example, I know

f(1) = 1;

f(2) = 2;

Then we can deduce f (3) = f (2) + f (1) = 3. Release can be f (4), f (5) and the like until f (n). Therefore, we can consider the use of bottom-up approach to self-recursive substitution, as follows:

 1	public int f(int n) {
 2       if(n <= 2)
 3           return n;
 4       int f1 = 1;
 5       int f2 = 2;
 6       int sum = 0;
 7
 8       for (int i = 3; i <= n; i++) {
 9           sum = f1 + f2;
10           f1 = f2;
11           f2 = sum;
12       }
13       return sum;
14   }

In this way, in fact, also known as recursive .

Concluded

In fact, recursion is not always from the top down, but also many of them are from the bottom up, for example, n = 1, has been recursively to n = 1000, such as some sort of combination.

Published 107 original articles · won praise 14 · views 40000 +

Guess you like

Origin blog.csdn.net/belongtocode/article/details/103135426