Recursion
Introduction
Want to get done the basics for some of this.
Why
Breaks down large chunks into smaller chunks.
When
You use it when problems contain smaller instances of the same problem. E.g. finding how many combinations there are of n items.
Aspects of Recursion
They all have a
- base case
- recursive case
Base Case
This is where the smallest instance of the case can be solved easily. E.g. For number of combinations when the array is empty.
Recursive Case
This is an instance of the problem that shrink the size of the input toward the base case.
Examples
Example Factorial
<syntaxhightlight lang="js"> const factorial = (n) => {
if(n === 1) return 1 return n * factorial(n-1)
} </syntaxhightlight>
Example Palindrome
const palindrome = (str) => {
console.log("calling with",str)
if(str.length === 0) return true
if(str.length === 1) return true
if(str[0] !== str[str.length-1]) return false
return palindrome(str.substr(1, str.length-2))
}
Example Sum
This was a bit of enlightenment for improving speed. Did the sum as you would normally do and you can see on line 3 we slice which means we copy.
const _sum = (numbers, idx) => {
if(numbers.length == 0) return 0
return n + sum(numbers.slice(1))
}
A better approach is to create a sub function which takes an index and therefore no more slices
const sum = (numbers) => {
return _sum(numbers, 0)
}
const _sum = (numbers, idx) => {
if(idx == numbers.length) return 0
return numbers[idx] + _sum(numbers, idx + 1)
}
Example String Reversal
This is where I struggle with recursive. Separating the a) reduce the argument and b) forming the result. It seems very odd to me. Maybe my dyslexic brain is fried
const string_rev = (str) => {
if(str.length === 0 || str.length === 1) return str
const result = string_rev(str.slice(1))
return result + str[0]
}
console.log("Reverse:", string_rev("FRED"))
So this is really
string_rev("RED") + "F" string_rev("ED") + "R" string_rev("D") + "E" string_rev("") + "D"
Example dec2bin
Dividing a number by 2 and concatenating the rem making a binary number
const dec2bin2 = (number) => {
if(number === 0 ) return 0
const quotent = Math.floor(number / 2)
const rem = (number % 2).toString()
const result = dec2bin(quotent)
return result + rem
}
Example Binary Search
find 10 in an array of [-1,0,1,2,3,4,7,9,10,20]. Key thing is in order. Took a while to solve this as I need floor and forgot to put brackets around the calculation - argggghhhhhhhhhh.
const binSearch = (val, numbers, left, right) => {
if(left > right) return -1
console.log("calling with left=%d, right=%d", left,right)
// Calculate the mid-point
const mid_point = Math.floor((left + right) / 2)
// const mid_point = (left+right) /2
console.log("mid_point now:%d", mid_point)
// Check if in middle
if(val === numbers[mid_point]) return mid_point
// Test is val is less than mid_point and search lefside
if(val < numbers[mid_point]) {
return binSearch(val, numbers, left, mid_point-1)
}
return binSearch(val, numbers, mid_point + 1, right)
}
console.log("dec2bin", binSearch(10,[-1,0,1,2,3,4,7,9,10,20], 0, 9))
Example Merge Sort
Kinda insane. This splits and array and for each half split down to two characters, sorts, unwinds so 4 characters, sorts until done
const merge = (array, start, mid, end, side) => {
// console.log("Merging start=%d, mid=%d, end=%d", start, mid, end);
console.log(">>--------------------------------")
console.log(array.slice(start, end + 1));
let i = start;
let j = mid + 1;
let k = 0;
let temp = Array(end - start);
while (i <= mid && j <= end) {
if (array[i] <= array[j]) {
temp[k++] = array[i++];
} else {
temp[k++] = array[j++];
}
}
// Add the rest of the values from left sub-array into the result
while (i <= mid) {
temp[k] = array[i];
k++;
i++;
}
// Add the rest of the values from right sub-array into the result
while (j <= end) {
temp[k] = array[j];
k++;
j++;
}
for (i = start; i <= end; i++) {
data[i] = temp[i - start];
}
console.log(array.slice(start, end + 1));
console.log("<<--------------------------------");
};
const mergeSort = (array, start, end) => {
if (array.length === 0) return [];
if (array.length === 1) return array;
if (start < end) {
const mid = Math.floor((start + end) / 2);
mergeSort(array, start, mid);
mergeSort(array, mid + 1, end);
merge(array, start, mid, end);
}
};
const data = [4, 1, 3, 2, 5, 6];
mergeSort(data, 0, data.length - 1);
console.log("Sorted data", data);
The output from merge looks like this for
input data [4, 1, 3, 2, 5, 6]; >>left --------------------------- [ 4, 1 ] [ 1, 4 ] <<-------------------------------- >>left ----------------------------- [ 1, 4, 3 ] [ 1, 3, 4 ] <<-------------------------------- >>right--------------------------- [ 2, 5 ] [ 2, 5 ] <<-------------------------------- >>right--------------------------- [ 2, 5, 6 ] [ 2, 5, 6 ] <<-------------------------------- >>all----------------------------- [ 1, 3, 4, 2, 5, 6 ] [ 1, 2, 3, 4, 5, 6 ] <<-------------------------------- output [ 1, 2, 3, 4, 5, 6 ]
Example Link List
Yum link lists my favourite. This is another head bender for me. I struggled to understand how link_list.next.next is not null. But got there with some debug.
const linklist_reverse = (linked_list) => {
// We dont process an empty linked list
// Or a linked list not link to anything
if(link_list === null) return link_list
if(linked_list.next === null) {
// We need a node with a next.next pointer
console.log("Base case reached with")
return linked_list
}
console.log("Calling with")
prettyPrintLinkedList(linked_list.next);
const result = linklist_reverse(linked_list.next)
console.log("changing node-----------")
prettyPrintLinkedList(linked_list.next);
console.log("using node-----------")
prettyPrintLinkedList(linked_list);
linked_list.next.next = linked_list
linked_list.next = null
console.log("Returning with")
prettyPrintLinkedList(result);
console.log("------------------------")
return result
}
And here is the debugging
Calling with B -> C -> D -> null Calling with C -> D -> null Calling with D -> null // Base case as we have no linked_list.next.next. 4/ returning D -> null 3/ changing node D -> null with C -> D -> null returning D -> C -> null 2/ changing node C -> null with B -> C -> null returning D -> C -> B -> null 1/ changing node B -> null with A -> B -> null returning D -> C -> B -> A -> null
Example Binary Search Tree Insert
The great thing about a bst is you do not need to move nodes to do and insert. Only identify the node it belongs to. And now the robot will build and pretty print it.
- left value must be less than the parent
- right value must be greater than the parent
const binary_search_insert = (bst, value) => {
if (bst === null) {
bst = { value, left: null, right: null };
return bst;
}
if (value < bst.value) {
bst.left = binary_search_insert(bst.left, value);
return bst;
} else {
bst.right = binary_search_insert(bst.right, value);
return bst;
}
};
DFS Depth First Search
This is where we traverse a graph going downwards first.
More Fib
With Fib we use two calls to the same function. This is known as multi branch recursion.
const fib = (n) => {
if(n < 0) return 0
if(n <= 1) return 1
if(n === 2) return 1
return fib(n-1) + fib(n-2)
}
Climbing Stairs (Recursion)
You are climbing a staircase. It takes n steps to reach the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Still trying to connect pictures with questions. So here goes for climb stairs. First a picture for climb stairs where there are 4 stairs. The successful paths (leaf nodes) in green
In the yellow square, the two child nodes, the left +1 step away, and the right +2 steps away. When you see a binary tree, and the children are k+1 and k+2 then you can use Fibonacci f(n) = f(n-1) + f(n-2) to calculate the number of leaf nodes
Combinations
Common questions are
- Given N things, in how many ways can we arrange them
- In how many ways can we do X
- What is the shortest way to do Y
What is a combination
It is a collection of things where the order does not matter. Important is give [a,b] in a combination it is the same as [b,a]. Given a set of n things, there are 2^n possible combinations.
Example
Looking at [a,b,c] this gives the following
To at each level, the left is if you don't take the level value. So at level a on the left branch we take the parent ([]), on the right we take the level value (a).