Review: Introduction to Recursive Programming
It’s a good book overall, but not outstanding. The content is not treated evenly between chapters. Nonetheless, there are some good chapters here and there:
- Chapter 1 and 2: The author does a good job introducing the concept of recursion intuitively with plenty of examples. Using diagram to decompose and combine problems is a good technique for one new to recursion.
- Chapters 5 to 9: There is a wide range of examples and exercises on different types of recursion. Some of them are from interview questions, which is a plus.
It could be a great book if the author demonstrated more examples with better explanations on how to think recursively. Specifically, I want more examples similar to what is done for the Fibonacci sequence.
Sometimes, especially in the later chapters, the author is too verbose in explaining the code line by line, which makes the reading experience less enjoyable.
Python is used throughout the book, which is too easy for me. So, I opted to explore Raku instead while following the examples in the book. Going back to the code examples, some are bad, particularly when the author uses lists to represent tree structures. Drawing fractals using recursion is a nice touch, though. I wish the book had more. The code for the recursive descent parser is too verbose for my taste.
Overall: 3.5/5. I definitely recommend some chapters of the book to newbies, but not the whole book.
Code Examples & Exercises
The recursive version of matrix multiplication (Chapter 6.10) is really not efficient since there are 2 matrix allocations occured at each level of recursion.
By simply removing the add_matrices_limits and use += instead of = in the base case, we do not have to allocate for the two temporary matrices.
A quick benchmark shows a significant speedup:
Benchmarking recur_matmul:76 ms, 107 μs, and 5 hnsecs
Benchmarking recur_matmul2:1 ms, 490 μs, and 7 hnsecs
mergesort suffers the similar issue, i.e., allocating new arrays at each level of recursion.
A better approach is to use an auxiliary array and swap the roles of the input and auxiliary arrays at each level of recursion as mentioned by (Sedgewick 1998).
void merge(int[] a, int[] aux, ulong low, ulong mid, ulong high) {
ulong i = low, j = mid + 1;
for (ulong k = low; k <= high; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > high) a[k] = aux[i++];
else if (aux[j] < aux[i]) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
void _mergesort(int[] a, int[] aux, ulong low, ulong high) {
if (high <= low) return;
auto mid = low + (high - low) / 2;
_mergesort(aux, a, low, mid);
_mergesort(aux, a, mid+1, high);
merge(a, aux, low, mid, high);
}
void mergesort(int[] a) {
int[] aux = a.dup;
_mergesort(a, aux, 0, a.length - 1);
}
References
- Sedgewick, Robert. 1998. Algorithms in C++, Parts 1 – 4: Fundamentals, Data Structure, Sorting, Searching. Pearson Education.