Data Structure and Algorithm Design
Chapter 5
Xingang (Ian) Fang
Sections
Recursion
Recursion in C++
Xingang (Ian) Fang
Outline
Overview
Recursive algorithms
Function call mechanism
Recursive functions in C++
Recursive data structures
Overview
Recursion: Defining a problem or an entity in terms of itself.
Recursive algorithm: An algorithm that breaks down the problem to smaller pieces and reuses itself to solve sub-problems.
Recursive data structure: A data structure that is defined in terms of a smaller version of itself.
Alternative to recursion: Iteration (loop).
Components:
Base case: the case that the problem can be solved directly.
Recursive case: the case that the problem can be broken down to smaller pieces.
Recursive algorithms
Example: Factorial
Base case: 0! = 1
Recursive case: n! = n * (n - 1)!
Implemented as recursive function in C++:
int factorial(int n) { if (n == 0) return 1; // base case else return n * factorial(n - 1); // recursive case }
Function call mechanism
Each function call creates a new stack frame in the stack memory.
The stack frame contains:
Function arguments.
Local variables.
Return address.
The stack frame is popped out of the stack memory when the function returns.
Stack overflow: When the stack memory is full. Usually caused by infinite recursion.
Recursive functions in C++
A function/method calls itself (directly or indirectly).
Forward declaration not mandatory except for mutual recursion (A calls B and B calls A)
Record keeping (parameters, return values, global/dynamic variable)
Helper functions (extra parameters for record keeping)
if statement
Recursive Linear Search Example
Linear search is a fundamental search algorithm that checks each element sequentially until the target is found or the end of the array is reached.
Recursion provides an elegant alternative to iteration for implementing linear search.
Three common approaches:
Helper function with index parameter
Improvement on method 1: default parameter (no helper function)
Backward search (compare tail element)
Method 1: Helper Function with Index Parameter
The most straightforward approach uses a helper function to manage the current index being checked:
int searchFirst(int *array, int size, int toSearch) {
return searchFirstHelper(array, size, toSearch, 0);
}
int searchFirstHelper(int *array, int size, int toSearch, int start) {
if (start >= size) // hit the end
return -1;
if (array[start] == toSearch) return start;
return searchFirstHelper(array, size, toSearch, start + 1);
}
The wrapper function calls a helper function that tracks the current starting index. This separates the public interface from the recursive implementation.
Method 2: Default Parameter (No Helper Function)
Using default parameters eliminates the need for a separate helper function:
int searchFirst1(int *array, int size, int toSearch, int start = 0) {
if (start >= size) // hit the end
return -1;
if (array[start] == toSearch) return start;
return searchFirst1(array, size, toSearch, start + 1);
}
This approach is cleaner for small implementations and allows callers to optionally provide a custom starting position.
Method 3: Backward Search (Compare Tail Element)
This method searches from the end of the array backward:
int searchLast(int *array, int size, int toSearch) {
if (size == 0) return -1;
if (array[size - 1] == toSearch) return size - 1;
else return searchLast(array, size - 1, toSearch);
}
Instead of incrementing an index from the beginning, this method decrements the size parameter, effectively checking the last element first and moving backward through the array.
Time complexity
Use recursive trees to estimate time complexity.
Multiplicative relationship
Rounds of recursive calls.
Cost per round of recursive calls.
Credit: zybooks.com
Recursive Data Structure in C++
A class holds instance or pointer/reference to instance of its own type
E.G.
Linked-list
Tree
Directory structure