|
| 1 | +// Copyright 2018-2025 the Deno authors. MIT license. |
| 2 | +// This module is browser compatible. |
| 3 | + |
| 4 | +import type { BinarySearchNode, Direction } from "./_binary_search_node.ts"; |
| 5 | +import { BinarySearchTree as StableBinarySearchTree } from "./binary_search_tree.ts"; |
| 6 | +import { internals } from "./_binary_search_tree_internals.ts"; |
| 7 | + |
| 8 | +const { |
| 9 | + getRoot, |
| 10 | + setRoot, |
| 11 | + setSize, |
| 12 | + getCompare, |
| 13 | +} = internals; |
| 14 | + |
| 15 | +/** |
| 16 | + * An unbalanced binary search tree. The values are in ascending order by default, |
| 17 | + * using JavaScript's built-in comparison operators to sort the values. |
| 18 | + * |
| 19 | + * For performance, it's recommended that you use a self-balancing binary search |
| 20 | + * tree instead of this one unless you are extending this to create a |
| 21 | + * self-balancing tree. See {@link RedBlackTree} for an example of how BinarySearchTree |
| 22 | + * can be extended to create a self-balancing binary search tree. |
| 23 | + * |
| 24 | + * | Method | Average Case | Worst Case | |
| 25 | + * | ------------- | ------------ | ---------- | |
| 26 | + * | find(value) | O(log n) | O(n) | |
| 27 | + * | insert(value) | O(log n) | O(n) | |
| 28 | + * | remove(value) | O(log n) | O(n) | |
| 29 | + * | min() | O(log n) | O(n) | |
| 30 | + * | max() | O(log n) | O(n) | |
| 31 | + * |
| 32 | + * @example Usage |
| 33 | + * ```ts |
| 34 | + * import { |
| 35 | + * BinarySearchTree, |
| 36 | + * ascend, |
| 37 | + * descend, |
| 38 | + * } from "@std/data-structures"; |
| 39 | + * import { assertEquals } from "@std/assert"; |
| 40 | + * |
| 41 | + * const values = [3, 10, 13, 4, 6, 7, 1, 14]; |
| 42 | + * const tree = new BinarySearchTree<number>(); |
| 43 | + * values.forEach((value) => tree.insert(value)); |
| 44 | + * assertEquals([...tree], [1, 3, 4, 6, 7, 10, 13, 14]); |
| 45 | + * assertEquals(tree.min(), 1); |
| 46 | + * assertEquals(tree.max(), 14); |
| 47 | + * assertEquals(tree.find(42), null); |
| 48 | + * assertEquals(tree.find(7), 7); |
| 49 | + * assertEquals(tree.remove(42), false); |
| 50 | + * assertEquals(tree.remove(7), true); |
| 51 | + * assertEquals([...tree], [1, 3, 4, 6, 10, 13, 14]); |
| 52 | + * |
| 53 | + * const invertedTree = new BinarySearchTree<number>(descend); |
| 54 | + * values.forEach((value) => invertedTree.insert(value)); |
| 55 | + * assertEquals([...invertedTree], [14, 13, 10, 7, 6, 4, 3, 1]); |
| 56 | + * assertEquals(invertedTree.min(), 14); |
| 57 | + * assertEquals(invertedTree.max(), 1); |
| 58 | + * assertEquals(invertedTree.find(42), null); |
| 59 | + * assertEquals(invertedTree.find(7), 7); |
| 60 | + * assertEquals(invertedTree.remove(42), false); |
| 61 | + * assertEquals(invertedTree.remove(7), true); |
| 62 | + * assertEquals([...invertedTree], [14, 13, 10, 6, 4, 3, 1]); |
| 63 | + * |
| 64 | + * const words = new BinarySearchTree<string>((a, b) => |
| 65 | + * ascend(a.length, b.length) || ascend(a, b) |
| 66 | + * ); |
| 67 | + * ["truck", "car", "helicopter", "tank", "train", "suv", "semi", "van"] |
| 68 | + * .forEach((value) => words.insert(value)); |
| 69 | + * assertEquals([...words], [ |
| 70 | + * "car", |
| 71 | + * "suv", |
| 72 | + * "van", |
| 73 | + * "semi", |
| 74 | + * "tank", |
| 75 | + * "train", |
| 76 | + * "truck", |
| 77 | + * "helicopter", |
| 78 | + * ]); |
| 79 | + * assertEquals(words.min(), "car"); |
| 80 | + * assertEquals(words.max(), "helicopter"); |
| 81 | + * assertEquals(words.find("scooter"), null); |
| 82 | + * assertEquals(words.find("tank"), "tank"); |
| 83 | + * assertEquals(words.remove("scooter"), false); |
| 84 | + * assertEquals(words.remove("tank"), true); |
| 85 | + * assertEquals([...words], [ |
| 86 | + * "car", |
| 87 | + * "suv", |
| 88 | + * "van", |
| 89 | + * "semi", |
| 90 | + * "train", |
| 91 | + * "truck", |
| 92 | + * "helicopter", |
| 93 | + * ]); |
| 94 | + * ``` |
| 95 | + * |
| 96 | + * @typeparam T The type of the values stored in the binary search tree. |
| 97 | + */ |
| 98 | +export class BinarySearchTree<T> extends StableBinarySearchTree<T> { |
| 99 | + /** |
| 100 | + * Construct an empty binary search tree. |
| 101 | + * |
| 102 | + * To create a binary search tree from an array like, an iterable object, or an |
| 103 | + * existing binary search tree, use the {@link BinarySearchTree.from} method. |
| 104 | + * |
| 105 | + * @param compare A custom comparison function to sort the values in the tree. |
| 106 | + * By default, the values are sorted in ascending order. |
| 107 | + */ |
| 108 | + constructor(compare?: (a: T, b: T) => number) { |
| 109 | + super(compare); |
| 110 | + } |
| 111 | + |
| 112 | + /** |
| 113 | + * Creates a new binary search tree from an array like, an iterable object, |
| 114 | + * or an existing binary search tree. |
| 115 | + * |
| 116 | + * A custom comparison function can be provided to sort the values in a |
| 117 | + * specific order. By default, the values are sorted in ascending order, |
| 118 | + * unless a {@link BinarySearchTree} is passed, in which case the comparison |
| 119 | + * function is copied from the input tree. |
| 120 | + * |
| 121 | + * @example Creating a binary search tree from an array like |
| 122 | + * ```ts no-assert |
| 123 | + * import { BinarySearchTree } from "@std/data-structures"; |
| 124 | + * |
| 125 | + * const tree = BinarySearchTree.from<number>([42, 43, 41]); |
| 126 | + * ``` |
| 127 | + * |
| 128 | + * @example Creating a binary search tree from an iterable object |
| 129 | + * ```ts no-assert |
| 130 | + * import { BinarySearchTree } from "@std/data-structures"; |
| 131 | + * |
| 132 | + * const tree = BinarySearchTree.from<number>((function*() { |
| 133 | + * yield 42; |
| 134 | + * yield 43; |
| 135 | + * yield 41; |
| 136 | + * })()); |
| 137 | + * ``` |
| 138 | + * |
| 139 | + * @example Creating a binary search tree from an existing binary search tree |
| 140 | + * ```ts no-assert |
| 141 | + * import { BinarySearchTree } from "@std/data-structures"; |
| 142 | + * |
| 143 | + * const tree = BinarySearchTree.from<number>([42, 43, 41]); |
| 144 | + * const copy = BinarySearchTree.from(tree); |
| 145 | + * ``` |
| 146 | + * |
| 147 | + * @example Creating a binary search tree from an array like with a custom comparison function |
| 148 | + * ```ts no-assert |
| 149 | + * import { BinarySearchTree, descend } from "@std/data-structures"; |
| 150 | + * |
| 151 | + * const tree = BinarySearchTree.from<number>( |
| 152 | + * [42, 43, 41], |
| 153 | + * { compare: descend } |
| 154 | + * ); |
| 155 | + * ``` |
| 156 | + * |
| 157 | + * @typeparam T The type of the values stored in the binary search tree. |
| 158 | + * @param collection An array like, an iterable, or existing binary search tree. |
| 159 | + * @param options An optional options object to customize the comparison function. |
| 160 | + * @returns A new binary search tree created from the passed collection. |
| 161 | + */ |
| 162 | + static override from<T>( |
| 163 | + collection: ArrayLike<T> | Iterable<T> | StableBinarySearchTree<T>, |
| 164 | + options?: { |
| 165 | + compare?: (a: T, b: T) => number; |
| 166 | + }, |
| 167 | + ): BinarySearchTree<T>; |
| 168 | + /** |
| 169 | + * Create a new binary search tree from an array like, an iterable object, or |
| 170 | + * an existing binary search tree. |
| 171 | + * |
| 172 | + * A custom mapping function can be provided to transform the values before |
| 173 | + * inserting them into the tree. |
| 174 | + * |
| 175 | + * A custom comparison function can be provided to sort the values in a |
| 176 | + * specific order. A custom mapping function can be provided to transform the |
| 177 | + * values before inserting them into the tree. By default, the values are |
| 178 | + * sorted in ascending order, unless a {@link BinarySearchTree} is passed, in |
| 179 | + * which case the comparison function is copied from the input tree. The |
| 180 | + * comparison operator is used to sort the values in the tree after mapping |
| 181 | + * the values. |
| 182 | + * |
| 183 | + * @example Creating a binary search tree from an array like with a custom mapping function |
| 184 | + * ```ts no-assert |
| 185 | + * import { BinarySearchTree } from "@std/data-structures"; |
| 186 | + * |
| 187 | + * const tree = BinarySearchTree.from<number, string>( |
| 188 | + * [42, 43, 41], |
| 189 | + * { map: (value) => value.toString() } |
| 190 | + * ); |
| 191 | + * ``` |
| 192 | + * |
| 193 | + * @typeparam T The type of the values in the passed collection. |
| 194 | + * @typeparam U The type of the values stored in the binary search tree. |
| 195 | + * @typeparam V The type of the `this` value when calling the mapping function. Defaults to `undefined`. |
| 196 | + * @param collection An array like, an iterable, or existing binary search tree. |
| 197 | + * @param options The options object to customize the mapping and comparison functions. The `thisArg` property can be used to set the `this` value when calling the mapping function. |
| 198 | + * @returns A new binary search tree containing the mapped values from the passed collection. |
| 199 | + */ |
| 200 | + static override from<T, U, V = undefined>( |
| 201 | + collection: ArrayLike<T> | Iterable<T> | StableBinarySearchTree<T>, |
| 202 | + options: { |
| 203 | + compare?: (a: U, b: U) => number; |
| 204 | + map: (value: T, index: number) => U; |
| 205 | + thisArg?: V; |
| 206 | + }, |
| 207 | + ): BinarySearchTree<U>; |
| 208 | + static override from<T, U, V>( |
| 209 | + collection: ArrayLike<T> | Iterable<T> | StableBinarySearchTree<T>, |
| 210 | + options?: { |
| 211 | + compare?: (a: U, b: U) => number; |
| 212 | + map?: (value: T, index: number) => U; |
| 213 | + thisArg?: V; |
| 214 | + }, |
| 215 | + ): BinarySearchTree<U> { |
| 216 | + const result = new BinarySearchTree<U>(options?.compare); |
| 217 | + const stableTree = super.from<T, U, V>( |
| 218 | + collection, |
| 219 | + // Cast to use types of `from<T, U, V = undefined>` instead of `from<T>`. |
| 220 | + // The latter happens by default, even though we call `super.from<T, U, V>`. |
| 221 | + options as { map: (value: T, index: number) => U }, |
| 222 | + ); |
| 223 | + setRoot(result, getRoot(stableTree)); |
| 224 | + setSize(result, stableTree.size); |
| 225 | + return result; |
| 226 | + } |
| 227 | + |
| 228 | + /** |
| 229 | + * Finds the node matching the given selection criteria. |
| 230 | + * |
| 231 | + * When searching for higher nodes, returns the lowest node that is higher than |
| 232 | + * the value. When searching for lower nodes, returns the highest node that is |
| 233 | + * lower than the value. |
| 234 | + * |
| 235 | + * By default, only accepts a node exactly matching the passed value and returns |
| 236 | + * it if found. |
| 237 | + * |
| 238 | + * @param value The value to search for |
| 239 | + * @param select Whether to accept nodes that are higher or lower than the value |
| 240 | + * @param returnIfFound Whether a node matching the value itself is accepted |
| 241 | + * @returns The node that matched, or null if none matched |
| 242 | + */ |
| 243 | + #findNode( |
| 244 | + value: T, |
| 245 | + select?: "higher" | "lower", |
| 246 | + returnIfFound: boolean = true, |
| 247 | + ): BinarySearchNode<T> | null { |
| 248 | + const compare = getCompare(this); |
| 249 | + |
| 250 | + let node: BinarySearchNode<T> | null = getRoot(this); |
| 251 | + let result: BinarySearchNode<T> | null = null; |
| 252 | + while (node) { |
| 253 | + const order = compare(value, node.value); |
| 254 | + if (order === 0 && returnIfFound) return node; |
| 255 | + |
| 256 | + let direction: Direction = order < 0 ? "left" : "right"; |
| 257 | + if (select === "higher" && order === 0) { |
| 258 | + direction = "right"; |
| 259 | + } else if (select === "lower" && order === 0) { |
| 260 | + direction = "left"; |
| 261 | + } |
| 262 | + |
| 263 | + if ( |
| 264 | + (select === "higher" && direction === "left") || |
| 265 | + (select === "lower" && direction === "right") |
| 266 | + ) { |
| 267 | + result = node; |
| 268 | + } |
| 269 | + |
| 270 | + node = node[direction]; |
| 271 | + } |
| 272 | + return result; |
| 273 | + } |
| 274 | + |
| 275 | + /** |
| 276 | + * Finds the lowest (leftmost) value in the binary search tree which is |
| 277 | + * greater than or equal to the given value, or null if the given value |
| 278 | + * is higher than all elements of the tree. |
| 279 | + * |
| 280 | + * The complexity of this operation depends on the underlying structure of the |
| 281 | + * tree. Refer to the documentation of the structure itself for more details. |
| 282 | + * |
| 283 | + * @example Finding values in the tree |
| 284 | + * ```ts |
| 285 | + * import { BinarySearchTree } from "@std/data-structures/unstable-binary-search-tree"; |
| 286 | + * import { assertEquals } from "@std/assert"; |
| 287 | + * |
| 288 | + * const tree = BinarySearchTree.from<number>([42]); |
| 289 | + * |
| 290 | + * assertEquals(tree.ceiling(41), 42); |
| 291 | + * assertEquals(tree.ceiling(42), 42); |
| 292 | + * assertEquals(tree.ceiling(43), null); |
| 293 | + * ``` |
| 294 | + * |
| 295 | + * @param value The value to search for in the binary search tree. |
| 296 | + * @returns The ceiling if it was found, or null if not. |
| 297 | + */ |
| 298 | + ceiling(value: T): T | null { |
| 299 | + return this.#findNode(value, "higher")?.value ?? null; |
| 300 | + } |
| 301 | + |
| 302 | + /** |
| 303 | + * Finds the highest (rightmost) value in the binary search tree which is |
| 304 | + * less than or equal to the given value, or null if the given value |
| 305 | + * is lower than all elements of the tree. |
| 306 | + * |
| 307 | + * The complexity of this operation depends on the underlying structure of the |
| 308 | + * tree. Refer to the documentation of the structure itself for more details. |
| 309 | + * |
| 310 | + * @example Finding values in the tree |
| 311 | + * ```ts |
| 312 | + * import { BinarySearchTree } from "@std/data-structures/unstable-binary-search-tree"; |
| 313 | + * import { assertEquals } from "@std/assert"; |
| 314 | + * |
| 315 | + * const tree = BinarySearchTree.from<number>([42]); |
| 316 | + * |
| 317 | + * assertEquals(tree.floor(41), null); |
| 318 | + * assertEquals(tree.floor(42), 42); |
| 319 | + * assertEquals(tree.floor(43), 42); |
| 320 | + * ``` |
| 321 | + * |
| 322 | + * @param value The value to search for in the binary search tree. |
| 323 | + * @returns The floor if it was found, or null if not. |
| 324 | + */ |
| 325 | + floor(value: T): T | null { |
| 326 | + return this.#findNode(value, "lower")?.value ?? null; |
| 327 | + } |
| 328 | + |
| 329 | + /** |
| 330 | + * Finds the lowest (leftmost) value in the binary search tree which is |
| 331 | + * strictly greater than the given value, or null if the given value |
| 332 | + * is higher than or equal to all elements of the tree |
| 333 | + * |
| 334 | + * The complexity of this operation depends on the underlying structure of the |
| 335 | + * tree. Refer to the documentation of the structure itself for more details. |
| 336 | + * |
| 337 | + * @example Finding values in the tree |
| 338 | + * ```ts |
| 339 | + * import { BinarySearchTree } from "@std/data-structures/unstable-binary-search-tree"; |
| 340 | + * import { assertEquals } from "@std/assert"; |
| 341 | + * |
| 342 | + * const tree = BinarySearchTree.from<number>([42]); |
| 343 | + * |
| 344 | + * assertEquals(tree.higher(41), 42); |
| 345 | + * assertEquals(tree.higher(42), null); |
| 346 | + * assertEquals(tree.higher(43), null); |
| 347 | + * ``` |
| 348 | + * |
| 349 | + * @param value The value to search for in the binary search tree. |
| 350 | + * @returns The higher value if it was found, or null if not. |
| 351 | + */ |
| 352 | + higher(value: T): T | null { |
| 353 | + return this.#findNode(value, "higher", false)?.value ?? null; |
| 354 | + } |
| 355 | + |
| 356 | + /** |
| 357 | + * Finds the highest (rightmost) value in the binary search tree which is |
| 358 | + * strictly less than the given value, or null if the given value |
| 359 | + * is lower than or equal to all elements of the tree |
| 360 | + * |
| 361 | + * The complexity of this operation depends on the underlying structure of the |
| 362 | + * tree. Refer to the documentation of the structure itself for more details. |
| 363 | + * |
| 364 | + * @example Finding values in the tree |
| 365 | + * ```ts |
| 366 | + * import { BinarySearchTree } from "@std/data-structures/unstable-binary-search-tree"; |
| 367 | + * import { assertEquals } from "@std/assert"; |
| 368 | + * |
| 369 | + * const tree = BinarySearchTree.from<number>([42]); |
| 370 | + * |
| 371 | + * assertEquals(tree.lower(41), null); |
| 372 | + * assertEquals(tree.lower(42), null); |
| 373 | + * assertEquals(tree.lower(43), 42); |
| 374 | + * ``` |
| 375 | + * |
| 376 | + * @param value The value to search for in the binary search tree. |
| 377 | + * @returns The lower value if it was found, or null if not. |
| 378 | + */ |
| 379 | + lower(value: T): T | null { |
| 380 | + return this.#findNode(value, "lower", false)?.value ?? null; |
| 381 | + } |
| 382 | +} |
0 commit comments