Source: BST.mjs

// 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 };