// Constants
const IN_ORDER = "in-order";
const PRE_ORDER = "pre-order";
const POST_ORDER = "post-order";
const REV_PRE_ORDER = "reverse-in-order";
const RM_PREDECESSOR = "predecessor";
const RM_SUCCESSOR = "successor";
/**
* TreeElement Class
* Represents an element of the BST
*/
class TreeElement {
/**
* @param {number} key - Unique key for the element
* @param {any=} [value] - Value of the element, null if not provided
*/
constructor(key, value = null) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
}
}
/**
* BST Class
* Binary Search Tree
*/
class BST {
/**
* BST Constructor
*/
constructor() {
this.root = null;
this.nodes = 0;
}
/**
* Clears the BST
*/
clear() {
this.root = null;
this.nodes = 0;
}
/**
* Insert a new element in the BST
* @param {number} key - Unique key for the element
* @param {any} [value] - Value of the element, null if not provided
* @returns {boolean} - True - If element was added
* @throws {Error} - If Element already exists in the BST
*/
add(key, value = null) {
const newNode = new TreeElement(key, value);
// If tree is empty, sets new element as tree root
if (!this.root) {
this.root = newNode;
this.nodes++;
return true;
}
const addNode = (current, newNode) => {
if (current.key > newNode.key) {
if (current.left) addNode(current.left, newNode);
else {
current.left = newNode;
this.nodes++;
}
} else if (current.key < newNode.key) {
if (current.right) addNode(current.right, newNode);
else {
current.right = newNode;
this.nodes++;
}
} else throw new Error("Node already exists");
};
addNode(this.root, newNode);
}
/**
* Removes an existing element in the BST
* @param {number} key - Unique key for the element
* @param {string} [type=predecessor] - Specifies delete type, allows 'predecessor' (default) for in-order predecessor, and 'successor' for in-order successor
* @returns {boolean} - True - If element was added; False if element does not exists
* @throws {Error} - If delete type is invalid
*/
remove(key, type = RM_PREDECESSOR) {
let parent = null;
let current = this.root;
while (current && current.key !== key) {
parent = current;
if (current.key > key) current = current.left;
else current = current.right;
}
if (!current) return false;
// Check children
if (!current.left && !current.right) {
// No children
if (!parent) this.root = null;
else if (parent.key > key) parent.left = null;
else parent.right = null;
this.nodes--;
} else if (current.left && !current.right) {
// Left child
if (!parent) this.root = current.left;
else if (parent.key > key) parent.left = current.left;
else parent.right = current.left;
this.nodes--;
} else if (current.right && !current.left) {
// Right child
if (!parent) this.root = current.right;
else if (parent.key > key) parent.left = current.right;
else parent.right = current.right;
this.nodes--;
} else {
// Both children are present
if (type === RM_PREDECESSOR) {
// Get Predecessor
let { node: predecessor, parent: predecessorParent } = this.#max(
current.left,
true
);
// Swap Predecessor with Current
[current.key, predecessor.key, current.value] = [
predecessor.key,
current.key,
predecessor.value,
];
// Rearrange children
if (predecessorParent.key === predecessor.key) {
current.left = predecessor.left;
} else if (predecessor.left) predecessorParent.right = predecessor.left;
else predecessorParent.right = null;
this.nodes--;
} else if (type === RM_SUCCESSOR) {
// Get Successor
let { node: successor, parent: successorParent } = this.#min(
current.right,
true
);
// Swap Successor with Current
[current.key, successor.key, current.value] = [
successor.key,
current.key,
successor.value,
];
// Rearrange children
if (successorParent.key === successor.key) {
current.right = successor.right;
} else if (successor.right) successorParent.left = successor.right;
else successorParent.left = null;
this.nodes--;
} else throw new Error("Invalid remove type");
}
return true;
}
/**
* Updates an existing element in the BST
* @param {number} key - Unique key for the element
* @param {any} [value] - Value of the element, null if not provided
* @returns {boolean} - True - If element was edited
* @throws {Error} - If element is not found
*/
update(key, value = null) {
let current = this.root;
while (current && current.key !== key) {
if (current.key > key) current = current.left;
else current = current.right;
}
if (current) {
current.value = value;
return true;
} else throw new Error("Node not found");
}
/**
* Returns an existing element in the BST
* @param {number} [key=null] - Unique key for the element, if null, return root of BST
* @returns {TreeElement} - Requested element, root of key is not provided
* @throws {Error} - If element is not found
*/
get(key = null) {
if (!key && key !== 0) return this.root; // If key is not provided return BST root
let current = this.root;
while (current && current.key !== key) {
if (current.key > key) current = current.left;
else current = current.right;
}
if (current) return current;
else throw new Error("Node not found");
}
/**
* Indicates if an element exists in the BST
* @param {number} key - Unique key for the element
* @returns {boolean} - True if element exists, false if element does not exists
*/
has(key) {
let current = this.root;
while (current && current.key !== key) {
if (current.key > key) current = current.left;
else current = current.right;
}
return current !== null;
}
/**
* Returns an array containing the key of the elements in the BST
* @param {number} key - Unique key for the element
* @param {string} [traversal="in-order"] - Specifies Traversal type, allows 'in-order' (default), 'pre-order', 'post-order', and 'reverse-in-order
* @returns {number[]} - Array of element keys
*/
toArray(traversal = IN_ORDER, node = this.root, output = []) {
if (![IN_ORDER, PRE_ORDER, POST_ORDER, REV_PRE_ORDER].includes(traversal))
throw new Error("Invalid traversal");
if (!node) return null;
switch (traversal) {
case IN_ORDER:
this.toArray(traversal, node.left, output);
output.push(node.key);
this.toArray(traversal, node.right, output);
break;
case PRE_ORDER:
output.push(node.key);
this.toArray(traversal, node.left, output);
this.toArray(traversal, node.right, output);
break;
case POST_ORDER:
this.toArray(traversal, node.left, output);
this.toArray(traversal, node.right, output);
output.push(node.key);
break;
case REV_PRE_ORDER:
this.toArray(traversal, node.right, output);
output.push(node.key);
this.toArray(traversal, node.left, output);
break;
}
return output;
}
// Getters
/**
* Returns max element in the BST
* @returns {TreeElement} - Max element, null if BST is empty
*/
get max() {
let node = this.root;
while (node.right) node = node.right;
return node;
}
/**
* Returns min element in the BST
* @returns {TreeElement} - Min element, null if BST is empty
*/
get min() {
let node = this.root;
while (node.left) node = node.left;
return node;
}
/**
* Returns number of element in the BST
* @returns {number} - Number of elements in the BST
*/
get size() {
return this.nodes;
}
/**
* Indicates if the BST is empty
* @returns {boolean} - True if BST is empty, false if there's at least one element present
*/
get isEmpty() {
return this.root === null;
}
// Private
// Return Max element from given subtree, returns parent of element if returnParent is set to True
#max(node, returnParent = false) {
let parent = node;
while (node.right) {
parent = node;
node = node.right;
}
return returnParent ? { node, parent } : { node, parent: null };
}
// Return Min element from given subtree, returns parent of element if returnParent is set to True
#min(node, returnParent = false) {
let parent = node;
while (node.left) {
parent = node;
node = node.left;
}
return returnParent ? { node, parent } : { node, parent: null };
}
}
export { BST };