Created
March 28, 2025 11:20
-
-
Save dustinboston/01fb60065d263877c943a7d9e59b2db6 to your computer and use it in GitHub Desktop.
Binary Search Tree
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * @file Binary Search Tree | |
| * @author Dustin Boston | |
| * @see Cormen, T, Leiserson, C, Rivest, R, and Stein, C. (2001). | |
| * Introduction to Algorithms. (2nd ed). The MIT Press. | |
| */ | |
| /** | |
| * Represents a node in a binary search tree. | |
| * | |
| * @template KeyType The type of the key stored in the node. | |
| */ | |
| export class BinarySearchTreeNode<KeyType> { | |
| /** | |
| * Creates an instance of a binary search tree node. | |
| * | |
| * @param key The key associated with the node. | |
| * @param leftChildNode A reference to the left child of the node. | |
| * @param rightChildNode A reference to the right child of the node. | |
| * @param parentNode A reference to the parent of the node. | |
| */ | |
| constructor( | |
| public key: KeyType, | |
| public leftChildNode?: BinarySearchTreeNode<KeyType>, | |
| public rightChildNode?: BinarySearchTreeNode<KeyType>, | |
| public parentNode?: BinarySearchTreeNode<KeyType>, | |
| ) {} | |
| } | |
| /** | |
| * Implements a generic binary search tree. | |
| * | |
| * @template KeyType The type of the keys stored in the tree. | |
| */ | |
| export class BinarySearchTree<KeyType> { | |
| /** | |
| * Initializes a new instance of the BinarySearchTree class. | |
| * | |
| * @param rootNode The root node of the tree. It defaults to undefined. | |
| */ | |
| constructor(public rootNode?: BinarySearchTreeNode<KeyType>) {} | |
| /** | |
| * Performs an in-order traversal of the tree, executing a function on each | |
| * node's key. | |
| * | |
| * @param startingNode The starting node of the traversal. | |
| */ | |
| inorderTreeWalk(startingNode?: BinarySearchTreeNode<KeyType>): void { | |
| if (startingNode !== undefined) { | |
| this.inorderTreeWalk(startingNode.leftChildNode); | |
| console.log(startingNode.key); | |
| this.inorderTreeWalk(startingNode.rightChildNode); | |
| } | |
| } | |
| /** | |
| * Recursively searches for a node with a given key. | |
| * | |
| * @param keyToFind The key to search for in the tree. | |
| * @param startingNode The node to start the search from. | |
| * @returns The node containing the key, or undefined if not found. | |
| */ | |
| treeSearch( | |
| keyToFind: KeyType, | |
| startingNode?: BinarySearchTreeNode<KeyType>, | |
| ): BinarySearchTreeNode<KeyType> | undefined { | |
| if (startingNode === undefined || keyToFind === startingNode.key) { | |
| return startingNode; | |
| } | |
| if (keyToFind < startingNode.key) { | |
| return this.treeSearch(keyToFind, startingNode.leftChildNode); | |
| } | |
| return this.treeSearch(keyToFind, startingNode.rightChildNode); | |
| } | |
| /** | |
| * Iteratively searches for a node with a given key. This is an alternative | |
| * to the recursive search method. It is more efficient for large trees and | |
| * avoids stack overflow errors. | |
| * | |
| * @param keyToFind The key to search for. | |
| * @param startingNode The node to start the search from, or root if | |
| * unspecified. | |
| * @returns The node containing the key, or undefined if not found. | |
| */ | |
| iterativeTreeSearch( | |
| keyToFind: KeyType, | |
| startingNode?: BinarySearchTreeNode<KeyType>, | |
| ): BinarySearchTreeNode<KeyType> | undefined { | |
| while (startingNode !== undefined && keyToFind !== startingNode.key) { | |
| startingNode = | |
| keyToFind < startingNode.key | |
| ? startingNode.leftChildNode | |
| : startingNode.rightChildNode; | |
| } | |
| return startingNode; | |
| } | |
| /** | |
| * Finds the node with the minimum key in the subtree rooted at the given | |
| * node. | |
| * | |
| * @param startingNode The root node of the subtree. | |
| * @returns The node with the smallest key in the subtree. | |
| */ | |
| getMinimum( | |
| startingNode: BinarySearchTreeNode<KeyType>, | |
| ): BinarySearchTreeNode<KeyType> { | |
| while (startingNode.leftChildNode !== undefined) { | |
| startingNode = startingNode.leftChildNode; | |
| } | |
| return startingNode; | |
| } | |
| /** | |
| * Finds the node with the maximum key in the subtree rooted at the given | |
| * node. | |
| * | |
| * @param startingNode The root node of the subtree. | |
| * @returns The node with the largest key in the subtree. | |
| */ | |
| getMaximum( | |
| startingNode: BinarySearchTreeNode<KeyType>, | |
| ): BinarySearchTreeNode<KeyType> { | |
| while (startingNode.rightChildNode !== undefined) { | |
| startingNode = startingNode.rightChildNode; | |
| } | |
| return startingNode; | |
| } | |
| /** | |
| * Finds the successor of a given node in the in-order traversal of the | |
| * tree. | |
| * | |
| * @param startingNode The node whose successor is to be found. | |
| * @returns The successor node if it exists, or undefined. | |
| */ | |
| getSuccessor( | |
| startingNode: BinarySearchTreeNode<KeyType>, | |
| ): BinarySearchTreeNode<KeyType> | undefined { | |
| if (startingNode.rightChildNode !== undefined) { | |
| return this.getMinimum(startingNode.rightChildNode); | |
| } | |
| let parentOfStartingNode = startingNode.parentNode; | |
| while ( | |
| parentOfStartingNode !== undefined && | |
| startingNode === parentOfStartingNode.rightChildNode | |
| ) { | |
| startingNode = parentOfStartingNode; | |
| parentOfStartingNode = parentOfStartingNode.parentNode; | |
| } | |
| return parentOfStartingNode; | |
| } | |
| /** | |
| * Inserts a new node into the binary search tree. | |
| * | |
| * @param newNode The new node to be inserted. | |
| * @returns The node that was inserted. | |
| */ | |
| insertNode( | |
| newNode: BinarySearchTreeNode<KeyType>, | |
| ): BinarySearchTreeNode<KeyType> { | |
| let parentNode: BinarySearchTreeNode<KeyType> | undefined; | |
| let currentNode: BinarySearchTreeNode<KeyType> | undefined = this.rootNode; | |
| while (currentNode !== undefined) { | |
| parentNode = currentNode; | |
| currentNode = | |
| newNode.key < currentNode.key | |
| ? currentNode.leftChildNode | |
| : currentNode.rightChildNode; | |
| } | |
| newNode.parentNode = parentNode; | |
| if (parentNode === undefined) { | |
| this.rootNode = newNode; | |
| } else if (newNode.key < parentNode.key) { | |
| parentNode.leftChildNode = newNode; | |
| } else { | |
| parentNode.rightChildNode = newNode; | |
| } | |
| return newNode; | |
| } | |
| /** | |
| * Deletes a node from the binary search tree. | |
| * | |
| * @param nodeToDelete The node to be deleted. | |
| * @returns The node that was removed from the tree, or undefined. | |
| */ | |
| deleteNode( | |
| nodeToDelete: BinarySearchTreeNode<KeyType>, | |
| ): BinarySearchTreeNode<KeyType> | undefined { | |
| const successorNode: BinarySearchTreeNode<KeyType> | undefined = | |
| this.getSuccessor(nodeToDelete) ?? nodeToDelete; | |
| const childNode: BinarySearchTreeNode<KeyType> | undefined = | |
| successorNode.leftChildNode ?? successorNode.rightChildNode; | |
| if (childNode !== undefined && successorNode.parentNode !== undefined) { | |
| childNode.parentNode = successorNode.parentNode; | |
| } | |
| if (successorNode.parentNode === undefined) { | |
| this.rootNode = childNode; | |
| } else if (successorNode === successorNode.parentNode.leftChildNode) { | |
| successorNode.parentNode.leftChildNode = childNode; | |
| } else { | |
| successorNode.parentNode.rightChildNode = childNode; | |
| } | |
| if (successorNode !== nodeToDelete) { | |
| nodeToDelete.key = successorNode.key; | |
| } | |
| return successorNode; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import {expect, test, describe} from "bun:test"; | |
| import {BinarySearchTree, BinarySearchTreeNode} from "./code.ts"; | |
| export function getTree() { | |
| const b = new BinarySearchTree<number>(); | |
| const n2 = new BinarySearchTreeNode(2); | |
| const n3 = new BinarySearchTreeNode(3); | |
| const n8 = new BinarySearchTreeNode(8); | |
| b.insertNode(n3); | |
| b.insertNode(n8); | |
| b.insertNode(n2); | |
| return {b, n2, n3, n8}; | |
| } | |
| describe("Binary Search Tree", () => { | |
| test("Insert", () => { | |
| const bst = getTree(); | |
| const {b, n3, n8, n2} = bst; | |
| expect(b.rootNode).toBeDefined(); | |
| expect(b.rootNode).toBe(n3); | |
| expect(b.rootNode?.leftChildNode).toBe(n2); | |
| expect(b.rootNode?.rightChildNode).toBe(n8); | |
| const n7 = new BinarySearchTreeNode(7); | |
| b.insertNode(n7); | |
| expect(b.rootNode?.rightChildNode?.leftChildNode).toBe(n7); | |
| }); | |
| test("Search", () => { | |
| const bst = getTree(); | |
| const {b, n8} = bst; | |
| expect(b.rootNode).toBeDefined(); | |
| const result = b.treeSearch(8, b.rootNode); | |
| expect(result).toBe(n8); | |
| const iterativeResult = b.iterativeTreeSearch(8, b.rootNode); | |
| expect(iterativeResult).toBe(n8); | |
| }); | |
| test("Min/Max", () => { | |
| const bst = getTree(); | |
| const {b, n8, n2} = bst; | |
| expect(b.rootNode).toBeDefined(); | |
| if (!b.rootNode) { | |
| throw new Error("Unexpected undefined root node."); | |
| } | |
| const maximumNode = b.getMaximum(b.rootNode); | |
| expect(maximumNode.key).toBe(n8.key); // Maximum key comparison | |
| const minimumNode = b.getMinimum(b.rootNode); | |
| expect(minimumNode.key).toBe(n2.key); // Minimum key comparison | |
| }); | |
| test("Delete", () => { | |
| const bst = getTree(); | |
| const {b, n2} = bst; | |
| expect(b.rootNode); | |
| const n7 = new BinarySearchTreeNode(7); | |
| b.insertNode(n7); | |
| expect(b.rootNode?.rightChildNode?.leftChildNode).toBe(n7); | |
| b.deleteNode(n7); | |
| expect(b.treeSearch(7, b.rootNode), undefined); | |
| const n1 = new BinarySearchTreeNode(1); | |
| b.insertNode(n1); | |
| b.deleteNode(n2); | |
| const result = b.treeSearch(2, b.rootNode); | |
| expect(result).toBeUndefined(); | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment