Recursion
[comp1110-2Z] - Liam O'Connor, Fabian Muehlboeck
Recursion
[comp1110-2Z]- Liam O'Connor, Fabian Muehlboeck
Definition 1. Recursive Type
[comp1110-7F] - Fabian Muehlboeck
Definition 1. Recursive Type
[comp1110-7F]- Fabian Muehlboeck
A recursive type is a type whose instances may "contain" other ("smaller") instances of the same type. It is typically described in terms of:
- At least one base case
- At least one recursive case (also called "inductive case")
Base cases describe those instances that do not contain further instances of the same type.
Recursive cases describe those instances that do contain further instances of the same type.
With this, we can build very large and potentially complex data structures.
Definition 2. Linked Lists
[comp1110-2S] - Liam O'Connor, Fabian Muehlboeck
Definition 2. Linked Lists
[comp1110-2S]- Liam O'Connor, Fabian Muehlboeck
A linked list is a way to store a sequence of items, one after another, where each item points to the next. Unlike arrays, it is not necessary to know ahead of time how many items the list will contain. It is a very common example of a recursive data type, where a type occurs as a field of itself. Here, we describe a linked list of integers by defining a recursive class type:
Here value is the first integer in the list, rest is the rest of the list (or null if there are no more integers). Therefore, null plays the role of the base case of our recursion,
while an IntList object (which contains another IntList) is the recursive case.
Definition 3. Recursive Function
[comp1110-7G] - Fabian Muehlboeck, Liam O'Connor
Definition 3. Recursive Function
[comp1110-7G]- Fabian Muehlboeck, Liam O'Connor
A recursive Function is a function that is defined in terms of itself. The idea here is that you can solve a problem by first solving a smaller part of it, and then adjusting that solution to become a solution for the whole problem. Like a recursive data type, it's code typically hase:
- At least one base case
- At least one recursive case (also called "inductive case")
Base cases describe those cases where a problem cannot be decomposed into smaller parts anymore - at this point, it should be easy to produce a solution for this relatively small problem.
Recursive cases describe those cases where you can first solve some smaller problems (by calling the same function, but with different input), and then combine those solutions into a solution for the current problem.
Recursive functions and recursive data types are a natural match, because the structure of the data can mirror the structure of your code: your function will have a base case for each base case of your recursive data type, and a recursive case for each recursive case of your recursive data type.
Example 4. Summing up a List of Integers
[comp1110-2U] - Liam O'Connor, Fabian Muehlboeck
Example 4. Summing up a List of Integers
[comp1110-2U]- Liam O'Connor, Fabian Muehlboeck
Recursive data types like linked lists are usually best handled by recursive functions:
However, in this case, because the last step performed in all recursive cases is to invoke the function recursively, We can translate the above program into a loop:
This works by starting at the first list cell, adding its value to the running total, then moving to the next cell by adjusting l to be l.next, and repeating until we reach null.
5. Stack Frames and Recursion
[comp1110-25] - Liam O'Connor, Fabian Muehlboeck
5. Stack Frames and Recursion
[comp1110-25]- Liam O'Connor, Fabian Muehlboeck
Compare Example 4 to this program that gives the same results:
All this program does is to give intermediate names to the results of looking at the value of an IntList node and of doing the recursive call.
This makes it much nicer to look at what is happening in the Java Visualizer (link to code above).
It also shows an important point about local variables.
Each time you call the sum function, the Java Visualizer shows you a new frame on top of the previous one.
These frames are part of the so-called call stack.
Local variables (the ones in the parameters of the function and the ones you define within the code) are local to each invocation of the recursive function, represented by a (call) stack frame.
Each frame corresponds to a particular function call that has not yet finished, and contains the local variables of that particular invocation.
This means that if you have n nested calls to sum, you will have n stack frames for sum on the call stack, on top of the frames from where sum was called originally (in our case, say, the main function).
Within those, there will be n different versions of l, each pointing to successive nodes in the linked list, plus at least n-1 versions of num,
each containing potentially different integer values.
If you change the value of a local variable within a function, you will only affect the value of that variable within the current function invocation's frame. All other frames will be unaffected, and when you call a function, the values of your local frame will be preserved until the function returns - the arguments get copied into the new frame.
The caveat here is when arguments are passed by reference, where the reference is copied to the new stack frame, but the value it points to is shared between caller and callee.
Definition 6. Stack Trace
[comp1110-7O] - Fabian Muehlboeck
Definition 6. Stack Trace
[comp1110-7O]- Fabian Muehlboeck
When your Java program crashes, it will typically print information about the exception that made it crash, along with a stack trace:
Here, the stack trace consits of the lines starting with the word "at". For every frame of your call stack, this will contain a line, in the same order as the call stack. That means that the top line is the place where the exception occurred. All the other lines indicate where the function whose frame is above that line was called.
This information can be very helpful when debugging your program, because it both tells you where the problem happened, and a lot of information about how you got there.
Warning 7. Stack Overflow
[comp1110-7P] - Fabian Muehlboeck
Warning 7. Stack Overflow
[comp1110-7P]- Fabian Muehlboeck
When using Recursion, your program might crash with a very long Stack Trace, and the specific exception is java.lang.StackOverflowError:
Essentially, your computer has some limited amount of space in its memory to store your stack frames, and when you have run out, a stack overflow happens, usually crashing your program.
This is usually because you got something wrong in writing your recursive code, and so your program loops forever, but with recursion, it eventually runs out of space. Make sure that your inputs to your recursive call are different (and "smaller") from the arguments to the current function!
Another possible reason is that you did write the recursive code correctly, but are operating on a data structure that is too large. This can happen, though you will usually not encounter such cases early in a computing degree. There are ways to increase the size reserved for stack frames, though it is not necessarily guaranteed that that will be a good solution. When working with large data, you might have to switch to iteration. However, don't see this as a reason to never use recursion - premature optimisation is never a good idea!
Definition 8. Tree
[comp1110-7H] - Fabian Muehlboeck
Definition 8. Tree
[comp1110-7H]- Fabian Muehlboeck
A tree is a recursive data type that consists of nodes, each of which may have some number of "child nodes", or children, which are themselves nodes of the tree. Each node has at most one parent, and the tree contains one node that has no parents, called the root of the tree. Nodes that have no children are also called leaves.
Each node can be treated as the root of a subtree starting with that node, ignoring its parent and any further ancestors.
When drawing a tree, we usually put the root on top, and the tree grows downwards. The following is a picture of a tree with a root node with two children. The first child only has one other child, while the second one has two more.
As the saying goes, this happened because computer scientists rarely go outside.
Trees are one of the most fundamental data structures in computer science. You will encounter them everywhere.
There is a large variety of potential tree data structures. Some have very rigid structures, where every node must either be a leaf or have the same number of children. Others allow nodes to have any number of children between some minimum or maximum. Some trees draw strict distinctions between leaves and inner nodes - oftentimes the distinction is that only one of them carries data. Some trees are formed by children knowing (pointing at) their parents, some trees are formed by parents knowing (point at) their children, in some trees, you can go in either direction.
Example 9. Linked Lists are Trees
[comp1110-7I] - Fabian Muehlboeck
Example 9. Linked Lists are Trees
[comp1110-7I]- Fabian Muehlboeck
A Linked List as defined earlier is a tree where nodes carry data and have either 0 or 1 children, and only parents know their children.
Example 10. Binary Trees
[comp1110-7J] - Fabian Muehlboeck
Example 10. Binary Trees
[comp1110-7J]- Fabian Muehlboeck
A Binary Tree is a tree where each node has 0 or 1 children on the "left", and 0 or 1 children on the "right" (for a total of between 0 and 2 children per node). Here is a suitable data definition:
We can now write a sum function to get the sum of all the numbers stored in the tree:
Note that because the tree has two fields that recursively point at other tree instances, we need to have two recursive function calls.
This example is where recursion gives you an advantage over iteration - in contrast to linked lists, it is much harder to write a while loop that does the same as the above recursive function. This is because the stack frame is now useful to store the intermediate result of calculating the sum of the values in the left child while we are calculating the sum of the values in the right child. If you try to write this with a loop, you will have to effectively manage a stack of such intermediate values yourself (there are some other tricks you will learn later in a computing degree that will sometimes let you avoid this, but nothing beats the recursion here in terms of its simplicity).
Example 11. The File System forms a Tree
[comp1110-7K] - Fabian Muehlboeck
Example 11. The File System forms a Tree
[comp1110-7K]- Fabian Muehlboeck
The file system mostly forms a tree where folders are nodes and files are leaves. Using the File class, you can get the children of a directory by calling listFiles, which returns an array of all files and directories contained within a directory:
Here, prefix is what is called an accumulator - as you go deeper into the recursion, the prefix string becomes bigger and bigger, accumulating information along the way.
Here, we use it to keep track of where we came from (essentially, it expresses the path from the root to the current node, as a string).
The Java File Class also has a getParentFile method, which allows you to access the parent of a node.
At the root of the file system, this returns null .