US Study Notes of Data Structures and Algorithms: Session Ten

First, the opening problem

Recommended registration back to the commission this function I think you should not be unfamiliar to you? Now a lot of App have this feature. This feature, the user A is recommended to register user B, user B and user C is recommended to register. We can say that the user C's "Final Recommended" for the user A,
user B "final recommendation" and also for the user A, the user A is no "final referees."

In general, we would recommend to record this relationship through a database. In a database table, we can record two ⾏ data, which represents actor_id user id, referrer_id represent Recommended id.

Based on this background, my question is, given a user ID, how to find the user's "Final Recommended"? With this issue, we learn today within the content, recursive (Recursion)

Second, how to understand recursion

1, cinema Case

On weekends, you go to the movies with his girlfriend, his girlfriend ask you, we are now sitting in the first few rows ah? Inside the cinema too dark to see, not the number, how do you do now?

2, using recursive solution to your problem in the first few rows

Do not forget you are a programmer, this can not beat you, recursive began rafts handy:

  1. So you ask the front row of the first few rows of person he is, as long as you want to add a number on him, to know that they came in what the.
  2. However, in front of people I could not see, ah, so he asked him in front of people. So rows and rows forward to ask, ask until the first row of people, I say in the first row,
  3. Such numbers then pass back and then row by row. Until the person in front of you to tell you where he was a few rows, then you will know the answer.

This is a pretty standard recursive decomposition process of solving the problem:

To process called "delivery", back process called "normalization" . Basically, all of recursion recursive formula can be used to represent. Just this life examples, we use the recursive formula it is represented like this:

f (n) = f (n-1) +1 wherein, f (1) = 1

Third, recursive need to meet three conditions

This is just a very typical example of recursion, then what kind of problems you can use recursion to solve it? I summed up the three conditions, as long as meet the following three conditions, you can use recursion to solve.

1. Solutions of a problem can be decomposed into several sub-solution of the problem

What is the problem child?

Sub-problem is the data size of smaller problems. For example, examples of speaking in front of the cinema,

You want to know, "which in its own row," the problem can be decomposed into "man in the front row of the row which" such a problem child.

2. The problem with sub-problems after decomposition, in addition to the different size of the data, solving thinking exactly the same

For example, a movie theater that example, you solved "their row in which" the idea, and in front of a line of people to solve "their row in which" the idea is exactly the same.

3. The presence of recursive termination condition

The problem into small problems, handle questions and then broken down into sub-sub-problems, layer by layer decomposition continues, there is not an infinite loop, which requires termination condition.

Or an example of the cinema, the first row do not need to continue to ask anyone, they know what the row, that is, f (1) = 1, which is the termination condition is recursive.

Fourth, how to write recursive Code

1, take the stairs case

If there are n number of steps, each time you cross a step or two steps, go ask these n steps how many ways? If there are seven steps you can
2,2,2,1 up like this, you can also 1,2,1,1,2 up like this, in short, there are a lot of law, how to use it to obtain a total of how many kinds of programming take the law?

1, the law of large problem into smaller problems

We want to carefully lower, in fact, all the methods can be divided into two categories according to the first step of the method,

  • The first category is the first step to go one level,
  • The other is the first step to go two steps.

So after method step is equal to n-1 order go, n-1 th step moves together with the second order go, n-2 and the walk step number. It is formulated

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

2, write recursive formula,

We can put f (2) = 2 as a termination condition, expressed walk two steps, there are two moves, one step or two steps to finish the walk.

3, deliberate termination condition,

Therefore, the recursive termination condition is f (1) = 1, f (2) = 2. This time, you can retake n = 3, n = 4 to verify that the termination conditions are adequate and correct.
We recursive and recursive formula termination condition just get put together is this:

f(1) = 1;
f(2) = 2;
f(n) = f(n-1)+f(n-2)

4, the recursion formulas and translated into code termination conditions.

With this formula, we converted into a recursive code is much simpler. The final recursive code is as follows:

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}

I summarize:

  1. Key to write the recursive code is to find how to break down big problems into small problems of law, and based on this write recursive formula,
  2. Then scrutiny termination condition
  3. Finally, the translation termination and conditions recursive formula into code

2, thinking errors recursive

Like the first example I just say, the human brain is almost no way to put the whole "delivery" and "normalization" process step by step, we want to clear.

1, a computer is good at doing repetitive things, so it's recursive positive and appetite.
2, the human brain and we prefer the straightforward way of thinking. When we see recursion, we always want to recursively expand tiled,
3, his mind will cycle, go down one level and then return one level,
4, tried to figure out the computer every step how to perform, so it is easy to go around.

For recursive code that tried to clear the entire delivery practices and normalization process of actually entering a thinking errors. In many cases, we have to understand more difficult, mainly because they own to create their own understanding of this disorder.

3, the correct understanding of recursive thinking

That should be the correct way of thinking what is it?

1, if a problem can be decomposed into several A word problem B, C, D,
2, you can assume that sub-problems B, C, D have been resolved, think about how to solve problems on the basis of A. Also, you only need to think about the relationship between the problem and submenus A problem B, C, D two,
3, do not need to think in layers and sub-sub-sub-problem problem down, and sub-sub-sub-sub-sub-problems the relationship between problem. Recursive details masked, like this much easier to understand.

Therefore, the key to writing recursive code is that as long as the encounter recursion, we put it abstracted into a recursive formula, do not want to call the relationship between the layers, and do not try to break down each step of employing a recursive brain.

V. recursive code to be vigilant Stack Overflow

1, a stack overflow hazards

Stack overflow will cause systemic collapse , the consequences will be very serious

2. Why recursive code is likely to cause a stack overflow it?

  • Function call stack will be used to store temporary variables.
  • Each call to a function, will be encapsulated into the temporary variable stack frame is pressed into the stack memory,
  • Function is executed, returns and other completed before the stack.

System stack or virtual machine stack space is generally not large. If the recursive solution of large-scale data, call a deep level, has been pushed onto the stack, there is a risk of stack overflow

Examples of such previously mentioned cinema, if we stack system or JVM stack size to 1KB, there will be given in the following stack solving f (19999):

Exception in thread "main" java.lang.StackOverflowError

3, how do we prevent stack overflow it?

We can limit the recursive calls in the code maximum depth way to solve this problem.

  • After the recursive call exceeds a certain depth (such as 1000), we can not continue down recursively, and directly return an error.
  • Or cinema that example, we may be transformed into the following like this, you can avoid the stack overflow.
  • However, I wrote the code is pseudo-code, the code for brevity, some boundary conditions are not considered, such as x <= 0.
// global variable, indicates the depth of recursion. 
depth = 0 int; 

int F (n-int) { 
  ++ depth; 
  IF (depth> 1000) the throw Exception; 
  
  IF (n-==. 1). 1 return; 
  return F (n--. 1) +. 1; 
}

However, this approach does not solve the problem because the maximum allowed recursion depth about remaining with the current thread stack space,

It can not be calculated in advance. If the real-time calculation, code is too complex, it will affect the readability of the code.

So, if the maximum depth comparative efficiency, such as 10, 50, you can use this method, otherwise this method is not very practical.

Sixth, recursive code to be wary of double-counting

1, illustrating double counting cases

In addition, there will be double counting when using recursion. The first example of a recursive code I have just talked about, if we put the whole recursive process breaks down as follows, then it is this:

From the figure, we can visually see, want to compute f (5), required to calculate f (4) and f (3), calculates f (4) need to calculate f (3), and therefore, F ( 3) was calculated many times, this is the problem of double counting.

2, how to avoid double counting

To avoid double counting, we can save had already solved f (k) by a data structure (such as a hash table).

When the recursive call when f (k) to next look at whether already solved before. If so, the process directly returns from the hash table values, need not be repeated calculations,

So as to avoid just talking about the problem.

According to the above ideas, we just look to transform the code:

F int public (n-int) { 
  IF (n-==. 1) return. 1; 
  IF (n-== 2) return 2; 
  
  // hasSolvedList can be understood as a Map, key is n, value is a F (n-) 
  IF (hasSolvedList .containsKey (n-)) { 
    return hasSolvedList.get (n-); 
  } 
  
  int RET = F (n--. 1) + F (n-2-); 
  hasSolvedList.put (n-, RET); 
  return RET; 
}

In addition to a stack overflow, these two common double counting problem. Recursive code that there are many other problems.

  • Efficiency in terms of time, a lot more code recursive function call, when a large number of these function calls, it will accumulate into a significant time cost.
  • In the space complexity, because a recursive call would save time field data in the memory stack,
  • Therefore, when analyzing the recursive code space complexity, consider the need for additional overhead of this section, such as we talked about earlier cinema recursive code is not the space complexity O (1), but O (n).

Seven, how to rewrite the code comes in handy recursive recursive code?

1, the advantages and disadvantages of recursive code

1, the advantages

  1. Expressive recursive code is very strong, very simple to write

2, shortcomings

  1. Space complexity is high
  2. Risk stack overflow
  3. Duplicate calculation
  4. Too many function calls and other issues will be more time-consuming

2, how to rewrite the code for a non-recursive recursive code?

That if we can rewrite the code for non-recursive recursive code? As just an example of the cinema, we put aside the scene, but only f (x) = f (x-1) +1 This recursive formula. We look at this rewrite:

int f (int n) { 
  int K = 1; 
  for (int i = 2; i <= n; ++ i) { 
    right = K + 1; 
  } 
  Return right; 
}

Similarly, the second example may be changed non-recursive implementation.

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  
  int ret = 0;
  int pre = 2;
  int prepre = 1;
  for (int i = 3; i <= n; ++i) {
    ret = pre + prepre;
    prepre = pre;
    pre = ret;
  }
  return ret;
}

3, it is not all recursive code can be changed to this iterative cycle of non-recursive writing it?

  1. Generally speaking, yes. Since the aid itself is a recursive stack to achieve, but we are using the system stack or virtual machine itself, provided we do not perceive nothing.
  2. If we own realization stack memory heap, manual simulation stack, the stack process, so any recursive code can be rewritten to look like is not recursive code
  3. But this idea is actually recursively changed to "Manual" recursion, nature has not changed, but also does not address some of the problems mentioned earlier, inviting the complexity of implementation

Eight, the answer begins

So far, the basic knowledge related to the recursive already finished, let's look at the problem begins: How to find the "ultimate Recommended"? My solution is this:

long findRootReferrerId(long actorId) {
  Long referrerId = select referrer_id from [table] where actor_id = actorId;
  if (referrerId == null) return actorId;
  return findRootReferrerId(referrerId);
}

It is not very simple? With three lines of code will be able to get, but in the actual project, the code above does not work, why? And there are two problems.

  1. First, if a deep recursion, there may be a stack overflow problems.
  2. Second, if there is dirty data ⾥ database, we also need to deal with infinite recursion resulting problems. For example demo database environment, test engineers in order to facilitate testing, artificially insert some data, there will be dirty data. Recommended if A is B, Recommended B is C, C is the recommended person A, so an infinite loop occurs .
  1. The first question, I have already answered them, can be used to limit the depth of recursion to solve.
  2. The second problem can also be solved with recursion depth limit.
  3.  However, there is a more advanced approach is to automatically detect the ABCA this "ring" of existence.

How to detect the presence of ring it? I do not elaborate on this, you can think about your next, we will talk about later chapters.

IX Summary

Knowledge of recursion, here even if all finished. Let me sum up.

  1. Recursion is a very efficient, simple coding skills. As long as the issue of "three conditions" is satisfied it can be solved by recursive code.
  2. But recursive code is also more difficult to write, hard to understand. Key to write recursive code is not to go around yourself, correct posture is to write recursive formula to find out the conditions for termination, and then translated into recursive code.
  3. Although recursive code is simple and efficient, but there are many drawbacks recursive codes. For example, a stack overflow, double counting, time-consuming function calls, space complexity draft, etc. So, when writing recursive code, we must control these side effects.

Ten, after-school thinking

We usually use the IDE's debug code like single-step tracking function, like the relatively large, deep level of recursion recursive code is almost impossible to use this debug mode. For recursive code, you have to debug any good way to do that?

  1. Print log found recursion value.
  2. Combined with conditional breakpoint debugging.
  3. The default is 998 layers python recursion

Guess you like

Origin www.cnblogs.com/luoahong/p/11819283.html