Binary trees are a fundamental data structure in computer science used to represent hierarchical relationships. They consist of nodes connected by edges and have a wide range of applications, from representing file systems to organizing data for efficient searching and sorting. By today we're here to talk about their simplest version binary tree
A binary tree is a tree with two main rules to follow:
The Node class represents each element in the binary tree. Each node has a value and pointers to its left and right children.
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
Explanation: Each node has a value property to store data as well as a left and right property representing pointers to the node's left and right children, respectively.
The BinarySearchTree class has only one property, the root node set to null by default.
class BinarySearchTree {
constructor() {
this.root = null;
}
}
The insert method adds a new node to the binary search tree. It ensures that the new node is placed in the correct position based on its value.
function insert(value) {
const newNode = new Node(value);
if (this.root === null) {
this.root = newNode;
return this;
}
let current = this.root;
while (true) {
if (value === current.value) return undefined;
if (value < current.value) {
if (current.left === null) {
current.left = newNode;
return this;
}
current = current.left;
} else {
if (current.right === null) {
current.right = newNode;
return this;
}
current = current.right;
}
}
}
Explanation:
The find method searches for a node with a specific value and returns the node if found. It traverses the tree starting from the root, comparing the target value with the current node's value to decide which subtree to traverse.
function find(value) {
if (this.root === null) return false;
let current = this.root;
let found = false;
while (current && !found) {
if (value < current.value) {
current = current.left;
} else if (value > current.value) {
current = current.right;
} else {
found = true;
}
}
if (!found) return undefined;
return current;
}
Explanation:
Here again we have a few cases to handle:
After the loop we basically have two possibilities:
The contains method checks if a value exists in the tree. It is similar to the find method but returns a boolean value indicating the presence of the value.
function contains(value) {
if (this.root === null) return false;
let current = this.root;
while (current) {
if (value < current.value) {
current = current.left;
} else if (value > current.value) {
current = current.right;
} else {
return true;
}
}
return false;
}
Explanation:
Same here we have a few cases to handle:
If we reach the end of the loop it means the value could not be found, so return false.
The remove method deletes a node with a specific value from the tree. We have three cases to handle, removing a leaf node, removing a node with one child, and removing a node with two children.
function remove(val) {
if (!this.root) return null;
let node = this.root;
if (node.value === val) {
if (node.left === null && node.right === null) {
this.root = null;
return node;
} else if (node.left !== null && node.right !== null) {
let right = node.right;
if (right.left === null) {
right.left = node.left;
this.root = right;
} else {
let rightParent = node;
while (right.left !== null) {
rightParent = right;
right = right.left;
}
rightParent.left = right.right;
right.left = node.left;
right.right = node.right;
this.root = right;
}
return node;
} else {
this.root = node.left || node.right;
return node;
}
}
let parentNode;
while (node.value !== val) {
parentNode = node;
if (node.value > val) {
node = node.left;
} else {
node = node.right;
}
}
if (node !== this.root) {
if (node.left === null && node.right === null) {
if (parentNode.left === node) {
parentNode.left = null;
} else {
parentNode.right = null;
}
} else if (node.left !== null && node.right !== null) {
let rightParent = node;
let right = node.right;
if (right.left === null) {
right.left = node.left;
if (parentNode.left === node) {
parentNode.left = right;
} else {
parentNode.right = right;
}
} else {
while (right.left !== null) {
rightParent = right;
right = right.left;
}
if (parentNode.left === node) {
parentNode.left.value = right.value;
} else {
parentNode.right.value = right.value;
}
if (right.right !== null) {
rightParent.left = right.right;
} else {
rightParent.left = null;
}
}
} else {
if (parentNode.left === node) {
if (node.right === null) {
parentNode.left = node.left;
} else {
parentNode.left = node.right;
}
} else {
if (node.right === null) {
parentNode.right = node.left;
} else {
parentNode.right = node.right;
}
}
}
}
return node;
}
Explanation:
We essentially have three cases to handle:
It ensures that after removal, the tree remains a valid binary search tree where for any given node:
The isBalanced method checks if the tree is balanced meaning the height difference between the left and right subtrees of every node is not more than 1.
function isBalanced(node = this.root) {
if (!node) {
return true;
}
return getHeightDiff(node) !== -1;
function getHeightDiff(node) {
if (node === null) return 0;
const leftHeight = getHeightDiff(node.left);
if (leftHeight === -1) return -1;
const rightHeight = getHeightDiff(node.right);
if (rightHeight === -1) return -1;
if (Math.abs(leftHeight - rightHeight) > 1) {
return -1;
} else {
return Math.max(leftHeight, rightHeight) + 1;
}
}
}
Explanation:
Here we are recursively checking if the tree is balanced and for that we choose the inner function pattern which to me allows for more clarity.
false. Otherwise, we return true.Traversing a tree involves visiting each node in a specific order. There are two main approaches: Depth First Search (DFS) and Breadth First Search (BFS).
DFS involves traversing as deep as possible along each branch before backtracking. From there, there are three resulting orders:
Push the current node, then it's left property, then it's right property until reaching the up most right leaf.
function DFSPreOrder() {
let result = [];
function traverse(node) {
result.push(node.value);
if (node.left) traverse(node.left);
if (node.right) traverse(node.right);
}
traverse(this.root);
return result;
}
Explanation:
Visit the left subtree, push the current node, then visit the right subtree.
function DFSInOrder() {
let result = [];
function traverse(node) {
if (node.left) traverse(node.left);
result.push(node.value);
if (node.right) traverse(node.right);
}
traverse(this.root);
return result;
}
Explanation: Similar to DFSPreOrder, but the node's value is pushed to result after traversing the left subtree and before traversing the right subtree.
Visit the left subtree, then the right subtree, then push the current node.
function DFSPostOrder() {
let result = [];
function traverse(node) {
if (node.left) traverse(node.left);
if (node.right) traverse(node.right);
result.push(node.value);
}
traverse(this.root);
return result;
}
Explanation:
Similar to DFSInOrder, but the node's value is pushed to result after traversing both the left and right subtrees.
BFS involves visiting all nodes at the present depth before moving on to the next depth level. We use a queue to keep track of the nodes to visit.
function BFS() {
let node = this.root;
let result = [];
let queue = [];
queue.push(node);
while (queue.length) {
node = queue.shift();
result.push(node.value);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
return result;
}
Explanation:
result array.Binary trees are used in various applications, including:
Congratulations 🎉! You made it through! I hope you learned a lot about binary trees and their implementation in JavaScript. If you have any suggestions, improvements, or questions, don't hesitate to write. I'll reply as soon as possible!
Happy coding!
Honestly i am no one, i've just been coding for 3 years now and i like to document every solutions to every problem i encounter. Extracting as much code snippets and tutorials i can so that if i ever need it again i just have to pop back here and get a quick refresher.
Feel free to me through this learning journey by providing any feedback and if you wanna support me: