Chapter 2: Recursion and Backtracking
Table of Contents
1. Recursion
Any function which calls itself is called recursive. A recursive method solves a problem by calling a copy of itself on a smaller problem. Recursion is most useful for tasks that can be defined in terms of similar subtasks.
Recursion code is generally shorter and easier to write than iterative code. Generally, loops are turned into recursive function when they are compiled or interpreted. Each recursive call makes a new copy of that method (actually only the variables) in memory. Once a method ends (that is, return some data), the copy of that returning method is removed from memory.
Recursive algorithm have two types of cases, recursive cases and base cases. Every recursive function case must terminate at a base case. Generally, iterative solutions are more efficient than recursive solutions (due to the overhead of function calls). A recursive algorithm can be implemented without recursive function calls using a stack, but it’s usually more trouble than its worth. That means any problem that can be solved recursively can also be solved iteratively.
2. Recursion versus Iteration
Recursion
- Terminates when a base case is reached.
- Each recursive call requires extra space on the stack frame (memory).
- If we get infinite recursion, the program may run out of memory and result in stack overflow.
- Solutions to some problems are easier to formulate recursively.
Iteration
- Terminates when a condition is proven to be false.
- Each iteration does not require extra space.
- An infinite loop could loop forever since there is no extra memory being created.
- Iterative solutions to a problem may not always be as obvious as a recursive solution.
3. Example Algorithms of Recursion
- Fibonacci Series, Factorial Finding
- Merge Sort, Quick Sort
- Binary Search
- Tree Traversals and many tree problems: InOrder, PreOrder, PostOrder
- Graph Traversals: DFS (Depth First Search) and BFS (Breadth First Search)
- Dynamic Programming Examples
- Divide and Conquer Algorithms
- Towers of Hanoi
- Backtracking Algorithm
4. Recursion: Problems & Solutions
Pro 1: Discuss Towers of Hanoi puzzle
solution: The Towers of Hanoi consists of three rods and a number of disks of different sizes which can slide onto any rod. The puzzle starts with the disks on one rod in ascending order of size, the smallest at the top thus making a conical shape. The objective is to move the entire stack to another rod, satisfying the following rules: Only one disk may be moved at a time; Each move consists of taking the upper disk from one of the rods and sliding it onto another rod, on top of the other disks that may already be present on that rod; No disk may be paced on top of a smaller disk.
Algorithm:
- Move the top n-1 disks from Source to Auxiliary tower,
- Move the nth disk from Source to Destination tower,
- Move the n-1 disks from Auxiliary tower to Destination tower.
- Transferring the top n-1 disks from Source to Auxiliary tower can again be thought of as a fresh problem and can be solved in the same manner. Once we solve with three disks, we can solve it with any number of disks with the above algorithm.
void TowerOfHanoi(int n, char sour, char dest, char auxi){
/*if only 1 disk, make the move and return*/
if(n==1){
printf("Move disk 1 from peg %c to peg %c", sour, dest);
return;
}
/*move top n-1 disk from A to B using C as auxiliary*/
TowerOfHanoi(n-1, sour, auxi, dest);
/*move remaining disk from A to C*/
printf("\n Move disk %d from peg %c to peg %c", n, sour, dest);
/*move n-1 disks from B to C using A as auxiliary*/
TowersOfHanoi(n-1, auxi,dest, sour);
}
Pro 2: Given an array check whether the array is sorted order with recursion
solution:
int isArrayInSortedOrder(int A[], int n){
if(n==1) return 1;
return (A[n-1]<A[n-2]) ? 0 :isArrayInSortedOrder(A, A-1);
}
Time Complexity: O(n). Space Complexity: O(n) for recursive stack space.
5. Backtracking
Backtracking is an improvement of the brute force approach. It systematically searches for a solution to a problem among all available options. It is a form of recursion and can be thought of as a selective tree/graph traversal method. At each node, we eliminate choices that are obviously not possible and proceed to recursively check only those that have potential. Backtracking speeds the exhaustive search by pruning.
6. Example Algorithm of Backtracking
- Binary String: Generating all binary strings
- Generating k-ary Strings
- N-Queens Problem
- The Knapsack Problem
- Generalized Strings
- Hamiltonian Cycles
- Graph Coloring Problems
7. Backtracking: Problems & Solutions
Pro 3: Generate all the strings of n bits
solution:
void Binary(int n){
if(n<1) printf("%s", A); //Assume array A is a global varible. A[0..n-1] array of size n.
else{
A[n-1]=0;
Binary(n-1);
A[n-1]=1;
Binary(n-1);
}
}
Pro 4: Generate all strings of length n drawn from 0…k-1
void k_string(int n, int k){
if(n<1) printf("%s",A);
else{
for(int j=0; j<k; j++){
A[n-1]=j;
k_string(n-1,k);
}
}
}
Pro 5: Finding the length of connected cells of 1s (regions) in an matrix of Os and 1s
Given a matrix, each of which may be 1 or 0. The filled cells that are connected form a region. Two cells are said to be connected if they are adjacent to each other horizontally, vertically or diagonally.
Solution: For each location traverse in all 8 directions and in each of those directions keep track of maximum region found.
int getval(int (*A)[5], int j, int j, int L, int H){
if(i<0 || i>=L|| j<0 || j>=H) return 0;
else return A[i][j];
}
void findMaxBlock(int (*A)[5], int r, int c, int L, int H, int size, bool **cntarr, int &maxsize){
if(r>=L|| c>H) return;
cntarr[r][c]=true;
size++;
if(size>maxsize) maxsize=size;
int direction[][2]={{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1}};
for(int i=0; i<8; i++){
int newi=r+direction[i][0];
int newj=c+direction[i][1];
int val=getval(A, newi, newj, L, H);
if(val>0 && (cntarr[newi][newj]==false)){
findMaxBlock(A, newi, newj, L, H, size, cntarr, maxsize);
}
}
cntarr[r][c]=false;
}
int getMaxOnes(int (*A)[5], int rmax, int colmax){
int maxsize=0;
int size=0;
bool **cntarr=create2darr(rmax,colmax);
for(int i=0; i<rmax; i++){
for(int j=0; j<colmax; j++){
if(A[i][j]==1)
findMaxBlock(A, i, j, rmax, colmax, 0, cntarr, maxsize);
}
}
return maxsize;
}