Add recursion

This commit is contained in:
Miloslav Ciz 2022-03-16 21:42:33 +01:00
parent 3bbd7ec31d
commit 49a1542551

40
recursion.md Normal file
View file

@ -0,0 +1,40 @@
# Recursion
In general recursion is a situation in which a [definition](definition.md) refers to itself; for example the definition of a human's ancestor as "the human's parents and the ancestors of his parents" ([fractals](fractal.md) are also very nice example of what a simple recursive definition can achieve). In programming recursion takes on a meaning of a **function that calls itself**; this is the meaning we'll suppose in this article, unless noted otherwise.
We divide recursion to a **direct** and **indirect** one. In direct recursion the function calls itself directly, in indirect function *A* calls a function `B` which ends up (even possibly by calling some more functions) calling *A* again. Indirect recursion is tricky because it may appear by mistake and cause a [bug](bug.md) (which is nevertheless easily noticed as the program will mostly run out of memory and crash).
When a function calls itself, it starts "diving" deeper and deeper and in most situations we want this to stop at some point, so in most cases **a recursion has to contain a terminating condition**. Without this condition the recursion will keep recurring and end up in an equivalent of an infinite loop (which in case of recursion will however crash the program with a [stack overflow](stack_overflow.md) exception). Let's see this on perhaps the most typical example of using recursion, a [factorial](factorial.md) function:
```
unsigned int factorial(unsigned int x)
{
if (x > 1)
return x * factorial(x - 1); // recursive call
else
return 1; // terminating condition
}
```
See that as long as x > 1, recursive calls are being made; with each the x is decremented so that inevitably x will at one point come to equal 1. Then the *else* branch of the condition will be taken -- the terminating condition has been met -- and in this branch no further recursive call is made, i.e. the recursion is stopped here and the code starts to descend from the recursion.
Note that even in computing we can use an infinite recursion sometimes. For example in [Hashell](haskell.md) it is possible to define infinite [data structures](data_structure.md) with a recursive definition; however this kind of recursion is intentionally allowed, it is treated as a mathematical definition and with correct use it won't crash the program.
**Every recursion can be replaced by iteration and vice versa** (iteration meaning a loop such as `while`). In fact some language (e.g. [functional](functional.md)) do not have loops and handle repetition solely by recursion. This means that you, a programmer, always have a choice between recursion and iteration, and here you should know that **recursion is typically slower than iteration**. This is because recursion has a lot of overhead: remember that every level of recursion is a function call that involves things such as pushing and popping values on stack, handling return addresses etc. The usual advice is therefore to **prefer iteration**, even though recursion can sometimes be more elegant/simple and if you don't mind the overhead, it's not necessarily wrong to go for it. Typically this is the case when you have multiple branches to dive into, e.g. in case of [quicksort](quicksort.md). In the above example of factorial we only have one recurring branch, so it's much better to implement the function with iteration:
```
unsigned int factorial(unsigned int x)
{
unsigned int result = 1;
while (x > 1)
{
result *= x;
x--;
}
return result;
}
```
How do the computers practically make recursion happen? Basically they use a [stack](stack.md) to remember states on each level of the recursion. In programming languages that support recursive function calls this is hidden behind the scenes in the form of [call stack](call_stack.md). This is why an infinite recursion causes stack overflow.