Chapter 8
Chapter 8
Traversing trees
Solving divide-and-conquer algorithms
Computing factorials, Fibonacci sequences, etc.
The factorial of a number n is the product of all positive integers less than or equal
to n. For example, 5! = 5×4×3×2×1
Recursive Formula:
n! = n×(n−1)!
Base Case: 0!=1
Example
#include <iostream>
int factorial(int n) {
if (n == 0) { // Base case
return 1;
}
else { // Recursive case
int main() {
int number;
// Calculating factorial
cout << "Factorial of " << number << " is " << result << endl;
return 0;
Step-by-Step Execution
For factorial(3):
Call factorial(3):
o Since 3≠0, it computes 3 × factorial(2).
Call factorial(2):
o Since 2≠0, it computes 2 × factorial(1).
Call factorial(1):
o Since 1≠0, it computes 1× factorial(0).
Call factorial(0):
o Since 0=0, it returns 1(base case).
Tail recursion
Tail recursion is a special form of recursion where the recursive call is the last
operation performed by the function before it returns. There is no computation
after the recursive call. This allows the compiler to optimize the recursion by
reusing the current function's stack frame, a process called tail call optimization
(TCO).
Example
#include <iostream>
int factorial(int n) {
int main() {
int number;
cout << "Factorial of " << number << " is " << factorial(number) << endl;
return 0;
1. When you can rewrite the recursion to avoid additional computation after the
recursive call.
2. For problems that can utilize an accumulator or equivalent state tracking.
Non-Tail Recursion
Non-tail recursion is a form of recursion where the recursive call is not the last
operation in the function. This means that after the recursive call, the function still
has more computations to perform before returning.
1. The function performs additional operations after the recursive call returns.
2. It cannot take advantage of tail call optimization (TCO), resulting in higher
memory usage as each recursive call needs to maintain its state in the call
stack.
3. Suitable for problems where intermediate computations depend on the
results of recursive calls.
Example: Non-Tail Recursion
#include <iostream>
int factorial(int n) {
if (n == 0) { // Base case
return 1;
int main() {
int number;
// Calculating factorial
cout << "Factorial of " << number << " is " << result << endl;
return 0;
Indirect Recursion
Indirect recursion occurs when a function calls another function, and that
function eventually calls the original function back, either directly or indirectly
through additional functions. This creates a cycle of function calls.
In indirect recursion, there are at least two functions involved, and the recursive
relationship between them is indirect. For example:
Example
#include <iostream>
bool isOdd(int n) {
bool isEven(int n) {
int main() {
int number;
if (isEven(number))
else
cout << number << " is Odd." << endl;
return 0;
If n=4 then?
Disadvantages
Key Takeaways
Nested Recursion
f(n)=1 if n=0
Example
#include <iostream>
int nestedRecursion(int n) {
int main() {
int number;
cout << "Enter a number: ";
return 0;
int main() {
int number;
cout << "Enter a number: ";
if (result != -1) {
} else {
return 0;
Excessive Recursion:
Recursion occurs when a function calls itself in order to break down a problem into
smaller subproblems. While recursion is a powerful technique, it can lead to issues
if the function doesn’t terminate or if it makes too many recursive calls before
reaching a base case.
Backtracking:
Backtracking is a general algorithmic technique used for finding all (or some)
solutions to problems that involve making a series of decisions. It is often used in
constraint satisfaction problems where the solution must satisfy certain
conditions, and we need to explore all possible combinations systematically while
eliminating those that are not valid.
Backtracking is often used in problems where you must explore all possible
solutions, and it is particularly useful in:
The N-Queens problem is a classic example where backtracking is used. The goal
is to place N queens on an NxN chessboard such that no two queens threaten each
other. This means:
Approach
1. Place a queen in the first row, then try to place another queen in the next
row.
2. For each row, place a queen in every column one by one and check whether
the current placement is valid (i.e., it doesn’t result in any queens attacking
each other).
3. If placing a queen in a particular column is not valid, backtrack and move
the queen to the next column.
4. Repeat the process for all rows until a solution is found or all possibilities
are exhausted.
#include <vector>
bool isSafe(const vector<vector<int> >& board, int row, int col, int n) {
if (board[i][col] == 1) {
return false;
if (board[i][j] == 1) {
return false;
if (board[i][j] == 1) {
return false;
return true;
if (row == n) {
// All queens are placed successfully
return true;
return true;
board[row][col] = 0;
int main() {
int n;
cin >> n;
if (solveNQueens(board, 0, n)) {
printBoard(board, n);
} else {
return 0;
}
Explanation of the Code
Backtracking Efficiency
However, backtracking can still be inefficient for large problems with a large
search space, and in some cases, it might require additional optimizations (e.g.,
dynamic programming, memoization, etc.) to improve performance.
Applications of Backtracking
1. Solving puzzles:
o Sudoku, crosswords, N-Queens problem, and many others.
2. Combinatorial problems:
o Generating permutations, combinations, subsets, etc.
3. Graph coloring:
o Finding ways to color a graph such that no two adjacent vertices have
the same color.
4. Pathfinding problems:
o Solving mazes and navigating grids.