diff --git a/CHANGELOG.md b/CHANGELOG.md index 29f9c34..3fb7941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [4.3.0] - 2021-08-09 +### Added +- `.floor` & `.ceil` as delegates to `.lowerBound` & `upperBound`. + +### Fixed +- `.lowerBound` & `upperBound` now finds the precise bound when multiple ones exist. +- make param (value) optional on `.insert`. + ## [4.2.2] - 2021-06-20 ### Fixed - index.d.ts - ## [4.2.1] - 2021-06-20 ### Fixed diff --git a/README.md b/README.md index 0a481b3..6725b06 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript * [.find(key)](#findkey) * [.min()](#min) * [.max()](#max) - * [.lowerBound(k)](#lowerboundk) - * [.upperBound(k)](#upperboundk) + * [.lowerBound(k[, includeEqual]) (floor)](#lowerboundk-includeEqual-floor) + * [.upperBound(k[, includeEqual]) (ceil)](#upperboundk-includeEqual-ceil) * [.root()](#root) * [.count()](#count) * [.traverseInOrder(cb)](#traverseinordercb) @@ -219,8 +219,8 @@ console.log(max.getKey()); // 90 console.log(max.getValue()); // v4 ``` -### .lowerBound(k) -finds the node with the biggest key less or equal a given value k. +### .lowerBound(k[, includeEqual]) (.floor) +finds the node with the biggest key less or equal a given value k. You can eliminate equal keys by passing second param as false. `.floor` is a delegate to the same function. @@ -229,19 +229,24 @@ finds the node with the biggest key less or equal a given value k. - +
runtime
k: T (number | string) + k: T (number | string) +
+ includeEqual: boolean +
BinarySearchTreeNode<T, U> | AvlTreeNode<T, U> O(log(n))
```js -console.log(bst.lowerBound(60).getKey()); // 50 +console.log(bst.lowerBound(60).getKey()); // 60 +console.log(bst.lowerBound(60, false).getKey()); // 50 console.log(bst.lowerBound(10)); // null ``` -### .upperBound(k) -finds the node with the smallest key bigger than a given value k. +### .upperBound(k[, includeEqual]) (.ceil) +finds the node with the smallest key bigger or equal a given value k. You can eliminate equal keys by passing second param as false. `.ceil` is a delegate to the same function. @@ -250,7 +255,11 @@ finds the node with the smallest key bigger than a given value k. - + @@ -258,6 +267,8 @@ finds the node with the smallest key bigger than a given value k. ```js console.log(bst.upperBound(75).getKey()); // 80 +console.log(bst.upperBound(80).getKey()); // 80 +console.log(bst.upperBound(80, false).getKey()); // 90 console.log(bst.upperBound(110)); // null ``` diff --git a/package.json b/package.json index 05b3137..1a7554c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@datastructures-js/binary-search-tree", - "version": "4.2.2", + "version": "4.3.0", "description": "binary search tree & avl tree (self balancing tree) implementation in javascript", "main": "index.js", "scripts": { diff --git a/src/avlTree.d.ts b/src/avlTree.d.ts index a750723..3bed1cd 100644 --- a/src/avlTree.d.ts +++ b/src/avlTree.d.ts @@ -2,12 +2,14 @@ import { BinarySearchTree } from './binarySearchTree'; import { AvlTreeNode } from './avlTreeNode'; export class AvlTree extends BinarySearchTree { - insert(key: T, value: U): AvlTreeNode; + insert(key: T, value?: U): AvlTreeNode; find(key: T): AvlTreeNode; max(node?: AvlTreeNode): AvlTreeNode; min(node?: AvlTreeNode): AvlTreeNode; - lowerBound(k: T, node?: AvlTreeNode): AvlTreeNode; - upperBound(k: T, node?: AvlTreeNode): AvlTreeNode; + lowerBound(k: T, includeEqual?: boolean): AvlTreeNode; + floor(k: T, includeEqual?: boolean): AvlTreeNode; + upperBound(k: T, includeEqual?: boolean): AvlTreeNode; + ceil(k: T, includeEqual?: boolean): AvlTreeNode; root(): AvlTreeNode; traverseInOrder(cb: (node: AvlTreeNode) => void): void; traversePreOrder(cb: (node: AvlTreeNode) => void): void; diff --git a/src/binarySearchTree.d.ts b/src/binarySearchTree.d.ts index dba0b5e..9c981b0 100644 --- a/src/binarySearchTree.d.ts +++ b/src/binarySearchTree.d.ts @@ -1,13 +1,15 @@ import { BinarySearchTreeNode } from './binarySearchTreeNode'; export class BinarySearchTree { - insert(key: T, value: U): BinarySearchTreeNode; + insert(key: T, value?: U): BinarySearchTreeNode; has(key: T): boolean; find(key: T): BinarySearchTreeNode; max(node?: BinarySearchTreeNode): BinarySearchTreeNode; min(node?: BinarySearchTreeNode): BinarySearchTreeNode; - lowerBound(k: T, node?: BinarySearchTreeNode): BinarySearchTreeNode; - upperBound(k: T, node?: BinarySearchTreeNode): BinarySearchTreeNode; + lowerBound(k: T, includeEqual?: boolean): BinarySearchTreeNode; + floor(k: T, includeEqual?: boolean): BinarySearchTreeNode; + upperBound(k: T, includeEqual?: boolean): BinarySearchTreeNode; + ceil(k: T, includeEqual?: boolean): BinarySearchTreeNode; root(): BinarySearchTreeNode; count(): number; remove(k: T): boolean; diff --git a/src/binarySearchTree.js b/src/binarySearchTree.js index 5cacd58..586dd7e 100644 --- a/src/binarySearchTree.js +++ b/src/binarySearchTree.js @@ -146,48 +146,80 @@ class BinarySearchTree { * Returns the node with the biggest key less or equal to k * @public * @param {number|string} k + * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - lowerBound(k, current = this._root) { - if (current === null) { - return null; - } + lowerBound(k, includeEqual = true) { + let lowerBound = null; - if (current.getKey() === k) { - return current; - } + const lowerBoundRecursive = (current) => { + if (current === null) { + return lowerBound; + } - if (current.getKey() > k) { - return this.lowerBound(k, current.getLeft()); - } + const currentKey = current.getKey(); + if (currentKey < k || (includeEqual && currentKey === k)) { + if (lowerBound === null || lowerBound.getKey() <= currentKey) { + lowerBound = current; + } + return lowerBoundRecursive(current.getRight()); + } - if (current.hasRight() && current.getRight().getKey() <= k) { - return this.lowerBound(k, current.getRight()); - } + return lowerBoundRecursive(current.getLeft()); + }; - return current; + return lowerBoundRecursive(this._root); } /** - * Returns the node with the smallest key bigger than k + * delegate to lowerBound * @public * @param {number|string} k + * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - upperBound(k, current = this._root) { - if (current === null) { - return null; - } + floor(k, includeEqual = true) { + return this.lowerBound(k, includeEqual); + } - if (current.getKey() <= k) { - return this.upperBound(k, current.getRight()); - } + /** + * Returns the node with the smallest key bigger or equal k + * @public + * @param {number|string} k + * @param {boolean} includeEqual + * @return {BinarySearchTreeNode|null} + */ + upperBound(k, includeEqual = true) { + let upperBound = null; - if (current.hasLeft() && current.getLeft().getKey() > k) { - return this.upperBound(k, current.getLeft()); - } + const upperBoundRecursive = (current) => { + if (current === null) { + return upperBound; + } - return current; + const currentKey = current.getKey(); + if (currentKey > k || (includeEqual && currentKey === k)) { + if (upperBound === null || upperBound.getKey() >= currentKey) { + upperBound = current; + } + return upperBoundRecursive(current.getLeft()); + } + + return upperBoundRecursive(current.getRight()); + }; + + return upperBoundRecursive(this._root); + } + + /** + * delegate to upperBound + * @public + * @param {number|string} k + * @param {boolean} includeEqual + * @return {BinarySearchTreeNode|null} + */ + ceil(k, includeEqual = true) { + return this.upperBound(k, includeEqual); } /** diff --git a/test/binarySearchTree.test.js b/test/binarySearchTree.test.js index cb48568..83d903f 100644 --- a/test/binarySearchTree.test.js +++ b/test/binarySearchTree.test.js @@ -82,22 +82,44 @@ describe('BinarySearchTree tests', () => { describe('.lowerBound(k)', () => { it('gets the node with biggest key less or equal k', () => { - expect(bst.lowerBound(60).getKey()).to.equal(50); + expect(bst.lowerBound(60).getKey()).to.equal(60); + expect(bst.lowerBound(60, false).getKey()).to.equal(50); }); it('returns null when k is less than all tree keys', () => { expect(bst.lowerBound(10)).to.equal(null); }); + + it('returns the biggest lower bound of multiple lower bounds', () => { + const lowerBst = new BinarySearchTree(); + lowerBst.insert(20); + lowerBst.insert(7); + lowerBst.insert(15); + lowerBst.insert(9); + expect(lowerBst.floor(10).getKey()).to.equal(9); + }); }); describe('.upperBound(k)', () => { it('gets the node with smallest key bigger than k', () => { expect(bst.upperBound(75).getKey()).to.equal(80); + expect(bst.upperBound(80).getKey()).to.equal(80); + expect(bst.upperBound(80, false).getKey()).to.equal(90); }); it('returns null when k is bigger than all tree keys', () => { expect(bst.upperBound(110)).to.equal(null); }); + + it('returns the smallest upper bound of multiple upper bounds', () => { + const upperBst = new BinarySearchTree(); + upperBst.insert(-133195046); + upperBst.insert(-49109668); + upperBst.insert(115062875); + upperBst.insert(-38206732); + upperBst.insert(49311742); + expect(upperBst.ceil(49303013).getKey()).to.equal(49311742); + }); }); describe('.traverseInOrder(cb)', () => {
runtime
k: T (number | string) + k: T (number | string) +
+ includeEqual: boolean +
BinarySearchTreeNode<T, U> | AvlTreeNode<T, U> O(log(n))