What is recursion and when should I use it?

2018-12-31 09:12发布

One of the topics that seems to come up regularly on mailing lists and online discussions is the merits (or lack thereof) of doing a Computer Science Degree. An argument that seems to come up time and again for the negative party is that they have been coding for some number of years and they have never used recursion.

So the question is:

  1. What is recursion?
  2. When would I use recursion?
  3. Why don't people use recursion?

30条回答
荒废的爱情
2楼-- · 2018-12-31 09:33

Recursion is solving a problem with a function that calls itself. A good example of this is a factorial function. Factorial is a math problem where factorial of 5, for example, is 5 * 4 * 3 * 2 * 1. This function solves this in C# for positive integers (not tested - there may be a bug).

public int Factorial(int n)
{
    if (n <= 1)
        return 1;

    return n * Factorial(n - 1);
}
查看更多
其实,你不懂
3楼-- · 2018-12-31 09:33

Recursion refers to a method which solves a problem by solving a smaller version of the problem and then using that result plus some other computation to formulate the answer to the original problem. Often times, in the process of solving the smaller version, the method will solve a yet smaller version of the problem, and so on, until it reaches a "base case" which is trivial to solve.

For instance, to calculate a factorial for the number X, one can represent it as X times the factorial of X-1. Thus, the method "recurses" to find the factorial of X-1, and then multiplies whatever it got by X to give a final answer. Of course, to find the factorial of X-1, it'll first calculate the factorial of X-2, and so on. The base case would be when X is 0 or 1, in which case it knows to return 1 since 0! = 1! = 1.

查看更多
梦该遗忘
4楼-- · 2018-12-31 09:33

This is an old question, but I want to add an answer from logistical point of view (i.e not from algorithm correctness point of view or performance point of view).

I use Java for work, and Java doesn't support nested function. As such, if I want to do recursion, I might have to define an external function (which exists only because my code bumps against Java's bureaucratic rule), or I might have to refactor the code altogether (which I really hate to do).

Thus, I often avoid recursion, and use stack operation instead, because recursion itself is essentially a stack operation.

查看更多
低头抚发
5楼-- · 2018-12-31 09:34

Here's a simple example: how many elements in a set. (there are better ways to count things, but this is a nice simple recursive example.)

First, we need two rules:

  1. if the set is empty, the count of items in the set is zero (duh!).
  2. if the set is not empty, the count is one plus the number of items in the set after one item is removed.

Suppose you have a set like this: [x x x]. let's count how many items there are.

  1. the set is [x x x] which is not empty, so we apply rule 2. the number of items is one plus the number of items in [x x] (i.e. we removed an item).
  2. the set is [x x], so we apply rule 2 again: one + number of items in [x].
  3. the set is [x], which still matches rule 2: one + number of items in [].
  4. Now the set is [], which matches rule 1: the count is zero!
  5. Now that we know the answer in step 4 (0), we can solve step 3 (1 + 0)
  6. Likewise, now that we know the answer in step 3 (1), we can solve step 2 (1 + 1)
  7. And finally now that we know the answer in step 2 (2), we can solve step 1 (1 + 2) and get the count of items in [x x x], which is 3. Hooray!

We can represent this as:

count of [x x x] = 1 + count of [x x]
                 = 1 + (1 + count of [x])
                 = 1 + (1 + (1 + count of []))
                 = 1 + (1 + (1 + 0)))
                 = 1 + (1 + (1))
                 = 1 + (2)
                 = 3

When applying a recursive solution, you usually have at least 2 rules:

  • the basis, the simple case which states what happens when you have "used up" all of your data. This is usually some variation of "if you are out of data to process, your answer is X"
  • the recursive rule, which states what happens if you still have data. This is usually some kind of rule that says "do something to make your data set smaller, and reapply your rules to the smaller data set."

If we translate the above to pseudocode, we get:

numberOfItems(set)
    if set is empty
        return 0
    else
        remove 1 item from set
        return 1 + numberOfItems(set)

There's a lot more useful examples (traversing a tree, for example) which I'm sure other people will cover.

查看更多
回忆,回不去的记忆
6楼-- · 2018-12-31 09:36

There are a number of good explanations of recursion in this thread, this answer is about why you shouldn't use it in most languages.* In the majority of major imperative language implementations (i.e. every major implementation of C, C++, Basic, Python, Ruby,Java, and C#) iteration is vastly preferable to recursion.

To see why, walk through the steps that the above languages use to call a function:

  1. space is carved out on the stack for the function's arguments and local variables
  2. the function's arguments are copied into this new space
  3. control jumps to the function
  4. the function's code runs
  5. the function's result is copied into a return value
  6. the stack is rewound to its previous position
  7. control jumps back to where the function was called

Doing all of these steps takes time, usually a little bit more than it takes to iterate through a loop. However, the real problem is in step #1. When many programs start, they allocate a single chunk of memory for their stack, and when they run out of that memory (often, but not always due to recursion), the program crashes due to a stack overflow.

So in these languages recursion is slower and it makes you vulnerable to crashing. There are still some arguments for using it though. In general, code written recursively is shorter and a bit more elegant, once you know how to read it.

There is a technique that language implementers can use called tail call optimization which can eliminate some classes of stack overflow. Put succinctly: if a function's return expression is simply the result of a function call, then you don't need to add a new level onto the stack, you can reuse the current one for the function being called. Regrettably, few imperative language-implementations have tail-call optimization built in.

* I love recursion. My favorite static language doesn't use loops at all, recursion is the only way to do something repeatedly. I just don't think that recursion is generally a good idea in languages that aren't tuned for it.

** By the way Mario, the typical name for your ArrangeString function is "join", and I'd be surprised if your language of choice doesn't already have an implementation of it.

查看更多
余欢
7楼-- · 2018-12-31 09:36

Simple english example of recursion.

A child couldn't sleep, so her mother told her a story about a little frog,
    who couldn't sleep, so the frog's mother told her a story about a little bear,
         who couldn't sleep, so the bear's mother told her a story about a little weasel... 
            who fell asleep.
         ...and the little bear fell asleep;
    ...and the little frog fell asleep;
...and the child fell asleep.
查看更多
登录 后发表回答