Correct Thinking Method for writing recursive functions

Source: Internet
Author: User

Recursion is a relatively difficult but important concept in programming. programmers who start learning programming from imperative languages have inherent defects in understanding this, this is even more true for programmers who want to learn programming from languages like C ++ that are unfriendly to functional programming paradigms. (For example, myself) It happened (unfortunately) that I was reading this book recently (this book has not been introduced in China, and there is only the original version sold by Amazon on the internet, I read the Chinese version on the Internet ), I was impressed by how Paul Graham wrote recursive functions in his book. because the original book is about Lisp, of course this part is also described using Lisp as an example. Considering that there are too few people reading this book in China, there will be no more people who can understand Lisp, I will reorganize it based on my understanding. most importantly, the original examples in the book are too few and too simple. I provide some additional and more complex examples myself. in order to have a better understanding of the problem.

  • What is recursion?
  • Understanding Recursion
  • Use recursion
  • Recursive Problems
  • Reference
What is recursion?

Iteration is human, recursion is God
-L. Peter Deutsch

Simple definition: "recursion occurs when a function calls itself directly or indirectly. it is simple to say, but it is complicated to understand, because recursion is not intuitive and does not conform to our thinking habits. Compared with recursion, iteration is easier to understand. because the way of thinking in our daily life is step by step, and we can understand the concept of doing one thing N times. there is almost no recursive thinking in our daily life.
A simple example is to calculate the length of a string in C/C ++. The following is a traditional method. We generally calculate the length through iteration, which is also easy to understand.

size_t length(const char *str) {  size_t length = 0;  while (*str != 0) {    ++length;    ++str;  }  return length;}

In fact, we can also perform such tasks through recursion.

size_t length(const char *str) {  if (*str == 0) {    return 0;  }  return length(++str) + 1;}

However, we do not do this. Although such implementation may sometimes have shorter code, it is obvious that it is more difficult to understand in terms of thinking. of course, I mean if you are not used to functional languages. this example is relatively simple. You can see it later.
The iteration algorithm can be described as follows: Judge each character of a string starting from the first character. When the character is not 0, the length of the string is increased by one.
The recursive algorithm can be described as follows: the length of the current string is equal to the length of the current string except the first character. The remaining string length is + 1.
As a simple example, the two algorithms are similar. Although we are used to iteration, we can also see that recursive algorithms are both described and actually implemented, it is not more difficult than iteration.

Understanding Recursion

When we are new to recursion, we can see a recursive implementation, and we will inevitably fall into continuous backtracking verification, because Backtracking is like thinking about iteration in turn, which is our habit of thinking, however, recursion does not need to be verified in this way. for example, another common example is the calculation of factorial. factorial: "The factorial of a positive integer (factorial) is the product of all positive integers smaller than or equal to this number, and the factorial of 0 is 1." The Ruby implementation is as follows:

def factorial(n)   if n <= 1 then    return 1  else    return n * factorial(n - 1)  endend

How can we determine whether the recursive calculation of this factorial is correct? Let's not talk about testing. How can we judge when we read the code?
This is the way we think about backtracking. For example, when n = 4factoria(4)Equal4 * factoria(3), Andfactoria(3)Equal3 * factoria(2),factoria(2)Equal2 * factoria(1), Equal2 * 1, Sofactoria(4)Equal4 * 3 * 2 * 1. The result is exactly the iteration definition of factorial 4.
Although we can use backtracking to verify whether n = a small value is correct, it is useless to understand it.
Paul Graham mentioned a method that inspired me a lot. The method is as follows:

This method is similar to mathematical induction and is also a correct way of thinking about recursion. In fact, the recursive expression of factorial is1!=1,n!=(n-1)!×n(See wiki). When the program implementation conforms to the algorithm description, the program is naturally right. If it is not correct, it is the algorithm itself wrong ...... Relatively speaking, n, n + 1 is a general situation. Although complicated, n, n + 1 is still understandable. The most important and most easily overlooked problem lies in 1st points, that is, the basic case (base case) must be correct. for example, in the above example, we removeif n <= 1After judgment, the Code will enter an endless loop and will never end.

Use recursion

Since recursion is harder to understand than iteration, why do we still need recursion? From the examples above, it is naturally meaningless, but many things are indeed easier to use recursive thinking ......
The classic example is the Fibonacci series. in mathematics, the Fibonacci series are defined by recursion:

· F0 = 0
· F1 = 1
· Fn = Fn-1 + Fn-2

With recursive algorithms, it is much simpler to implement them using programs:

def fibonacci(n)  if n == 0 then    return 0  elsif n == 1 then    return 1  else    return fibonacci(n - 1) + fibonacci(n - 2)  endend

Instead, what about iterative implementation? You can try.
The above explains how to understand recursion is correct. At the same time, we can see that after a recursive algorithm is described, the program is easy to write. The most critical issue is, how can we find a problematic recursive algorithm?
Paul Graham mentioned that you only need to do two things:

This process is still a mathematical induction method, but it is a verification and proof as mentioned above.
Now we are using this method to find a solution for the tower of liberty game (this is actually a game invented by mathematicians)

There are three poles A, B, and C. There are N (N> 1) perforated disks on rod A, and the size of the disks decreases from bottom to top. Move all disks to the C rod according to the following rules:
1. Only one disc can be moved at a time.
2. The dashboard cannot be stacked on a small disk.


This game is easy to play when there are only three disks. The more disks there are, the harder it will be, you will enter a state that constantly uses backtracking to deduce what to do next. This is rather difficult. I remember the first time I met this game, it seems like it was in a generation of games in the great ocean era, and it was quite interesting. we recommend that you have a real experience playing this game. You can try to find out how many disks you have.
Now let's use Paul Graham's method to think about this game.

General situation:
When there are N disks on A, we have found A way to move them to the C barrier. How can we move N + 1 disc to the C barrier? It is very simple. First we use the method of moving N disks to C to move N disks to B, and then move the N + 1 disc (the last one) to C, in the same way, the N disks on B barrier are moved to C. solve the problem.

Basic use cases:
When there is A disc on A, we can directly move the disc to C.

The algorithm description is like this. It can also be considered as a process of thinking, which is relatively natural. The following is a Ruby solution:

def hanoi(n, from, to, other)  if n == 1 then    puts from + ' -> ' + to  else    hanoi(n-1, from, other, to)    hanoi(1, from, to, other)    hanoi(n-1, other, to, from)  endend

Output when n = 3:

A-> C
A-> B
C-> B
A-> C
B->
B-> C
A-> C

In the above Code, the role of from, to, and other is actually to provide a pole replacement character. When n = 1, it is actually equivalent to moving directly. it seems that this complicated problem is actually so easy to use recursion. what if I want to use iteration to solve this problem? You can try it on your own. The more you try, the better you will be able to experience the advantages of recursion.

Recursive Problems

Of course, there is no universal solution in this world, and recursion is no exception. First, recursion is not necessarily applicable to all situations. In many cases, iteration is much better than recursion. Second, comparatively speaking, the recursion efficiency is often lower than the implementation of iteration. At the same time, the memory is more useful. Although tail recursion can be used for optimization at this time, tail recursion is not necessarily simple.

Reference

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.