Skip to content

Commit

Permalink
docs: more example code for RedBlackTree and BinarySearchTree
Browse files Browse the repository at this point in the history
  • Loading branch information
zrwusa committed Nov 24, 2024
1 parent 4298ac6 commit 1494ffa
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 158 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-structure-typed",
"version": "1.53.7",
"version": "1.53.8",
"description": "Javascript Data Structure. Heap, Binary Tree, Red Black Tree, Linked List, Deque, Trie, HashMap, Directed Graph, Undirected Graph, Binary Search Tree(BST), AVL Tree, Priority Queue, Graph, Queue, Tree Multiset, Singly Linked List, Doubly Linked List, Max Heap, Max Priority Queue, Min Heap, Min Priority Queue, Stack. Benchmark compared with C++ STL. API aligned with ES6 and Java.util. Usability is comparable to Python",
"main": "dist/cjs/index.js",
"module": "dist/mjs/index.js",
Expand Down
58 changes: 31 additions & 27 deletions src/data-structures/binary-tree/bst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,32 +95,48 @@ export class BSTNode<K = any, V = any, NODE extends BSTNode<K, V, NODE> = BSTNod
* 6. Balance Variability: Can become unbalanced; special types maintain balance.
* 7. No Auto-Balancing: Standard BSTs don't automatically balance themselves.
* @example
* // Find kth smallest element
* // Create a BST with some elements
* const bst = new BST<number>([5, 3, 7, 1, 4, 6, 8]);
* const sortedKeys = bst.dfs(node => node.key, 'IN');
* // Merge 3 sorted datasets
* const dataset1 = new BST<number, string>([
* [1, 'A'],
* [7, 'G']
* ]);
* const dataset2 = [
* [2, 'B'],
* [6, 'F']
* ];
* const dataset3 = new BST<number, string>([
* [3, 'C'],
* [5, 'E'],
* [4, 'D']
* ]);
*
* // Helper function to find kth smallest
* const findKthSmallest = (k: number): number | undefined => {
* return sortedKeys[k - 1];
* };
* // Merge datasets into a single BinarySearchTree
* const merged = new BST<number, string>(dataset1);
* merged.addMany(dataset2);
* merged.merge(dataset3);
*
* // Assertions
* console.log(findKthSmallest(1)); // 1
* console.log(findKthSmallest(3)); // 4
* console.log(findKthSmallest(7)); // 8
* // Verify merged dataset is in sorted order
* console.log([...merged.values()]); // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
* @example
* // Find elements in a range
* const bst = new BST<number>([10, 5, 15, 3, 7, 12, 18]);
* console.log(bst.search(new Range(5, 10))); // [10, 5, 7]
* console.log(bst.search(new Range(4, 12))); // [10, 12, 5, 7]
* console.log(bst.rangeSearch([4, 12], node => node.key.toString())); // ['10', '12', '5', '7']
* console.log(bst.search(new Range(4, 12, true, false))); // [10, 5, 7]
* console.log(bst.search(new Range(15, 20))); // [15, 18]
* console.log(bst.rangeSearch([15, 20])); // [15, 18]
* console.log(bst.search(new Range(15, 20, false))); // [18]
* @example
* // Find lowest common ancestor
* const bst = new BST<number>([20, 10, 30, 5, 15, 25, 35, 3, 7, 12, 18]);
*
* // LCA helper function
* const findLCA = (num1: number, num2: number): number | undefined => {
* const path1 = bst.getPathToRoot(num1);
* const path2 = bst.getPathToRoot(num2);
* // Find the first common ancestor
* return findFirstCommon(path1, path2);
* };
*
* function findFirstCommon(arr1: number[], arr2: number[]): number | undefined {
* for (const num of arr1) {
* if (arr2.indexOf(num) !== -1) {
Expand All @@ -130,14 +146,6 @@ export class BSTNode<K = any, V = any, NODE extends BSTNode<K, V, NODE> = BSTNod
* return undefined;
* }
*
* // LCA helper function
* const findLCA = (num1: number, num2: number): number | undefined => {
* const path1 = bst.getPathToRoot(num1);
* const path2 = bst.getPathToRoot(num2);
* // Find the first common ancestor
* return findFirstCommon(path1, path2);
* };
*
* // Assertions
* console.log(findLCA(3, 10)); // 7
* console.log(findLCA(5, 35)); // 15
Expand Down Expand Up @@ -616,9 +624,6 @@ export class BST<
* The `rangeSearch` function searches for nodes within a specified range in a binary search tree.
* @param {Range<K> | [K, K]} range - The `range` parameter in the `rangeSearch` function can be
* either a `Range` object or an array of two elements representing the range boundaries.
* @param [onlyOne=false] - The `onlyOne` parameter is a boolean flag that indicates whether you want
* to stop the search after finding the first matching node within the specified range. If `onlyOne`
* is set to `true`, the search will return as soon as a single matching node is found. If `onlyOne`
* @param {C} callback - The `callback` parameter in the `rangeSearch` function is a callback
* function that is used to process each node that is found within the specified range during the
* search operation. It is of type `NodeCallback<NODE>`, where `NODE` is the type of nodes in the
Expand All @@ -635,13 +640,12 @@ export class BST<
*/
rangeSearch<C extends NodeCallback<NODE>>(
range: Range<K> | [K, K],
onlyOne = false,
callback: C = this._DEFAULT_NODE_CALLBACK as C,
startNode: BTNRep<K, V, NODE> | R = this._root,
iterationType: IterationType = this.iterationType
) {
const searchRange: Range<K> = range instanceof Range ? range : new Range(range[0], range[1]);
return this.search(searchRange, onlyOne, callback, startNode, iterationType);
return this.search(searchRange, false, callback, startNode, iterationType);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/data-structures/binary-tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ export * from './bst';
export * from './binary-indexed-tree';
export * from './segment-tree';
export * from './avl-tree';
export * from './rb-tree';
export * from './red-black-tree';
export * from './avl-tree-multi-map';
export * from './tree-multi-map';
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,58 @@ export class RedBlackTreeNode<
/**
* 1. Efficient self-balancing, but not completely balanced. Compared with AVLTree, the addition and deletion efficiency is high but the query efficiency is slightly lower.
* 2. It is BST itself. Compared with Heap which is not completely ordered, RedBlackTree is completely ordered.
* @example
* // Find elements in a range
* const bst = new RedBlackTree<number>([10, 5, 15, 3, 7, 12, 18]);
* console.log(bst.search(new Range(5, 10))); // [5, 10, 7]
* console.log(bst.search(new Range(4, 12))); // [5, 10, 12, 7]
* console.log(bst.search(new Range(15, 20))); // [15, 18]
* @example
* // using Red-Black Tree as a price-based index for stock data
* // Define the structure of individual stock records
* interface StockRecord {
* price: number; // Stock price (key for indexing)
* symbol: string; // Stock ticker symbol
* volume: number; // Trade volume
* }
*
* // Simulate stock market data as it might come from an external feed
* const marketStockData: StockRecord[] = [
* { price: 142.5, symbol: 'AAPL', volume: 1000000 },
* { price: 335.2, symbol: 'MSFT', volume: 800000 },
* { price: 3285.04, symbol: 'AMZN', volume: 500000 },
* { price: 267.98, symbol: 'META', volume: 750000 },
* { price: 234.57, symbol: 'GOOGL', volume: 900000 }
* ];
*
* // Extend the stock record type to include metadata for database usage
* type StockTableRecord = StockRecord & { lastUpdated: Date };
*
* // Create a Red-Black Tree to index stock records by price
* // Simulates a database index with stock price as the key for quick lookups
* const priceIndex = new RedBlackTree<number, StockTableRecord, StockRecord>(
* marketStockData,
* {
* toEntryFn: stockRecord => [
* stockRecord.price, // Use stock price as the key
* {
* ...stockRecord,
* lastUpdated: new Date() // Add a timestamp for when the record was indexed
* }
* ]
* }
* );
*
* // Query the stock with the highest price
* const highestPricedStock = priceIndex.getRightMost();
* console.log(priceIndex.get(highestPricedStock)?.symbol); // 'AMZN' // Amazon has the highest price
*
* // Query stocks within a specific price range (200 to 400)
* const stocksInRange = priceIndex.rangeSearch(
* [200, 400], // Price range
* node => priceIndex.get(node)?.symbol // Extract stock symbols for the result
* );
* console.log(stocksInRange); // ['GOOGL', 'MSFT', 'META']
*/
export class RedBlackTree<
K = any,
Expand Down
2 changes: 1 addition & 1 deletion src/data-structures/binary-tree/tree-multi-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
TreeMultiMapOptions
} from '../../types';
import { IBinaryTree } from '../../interfaces';
import { RedBlackTree, RedBlackTreeNode } from './rb-tree';
import { RedBlackTree, RedBlackTreeNode } from './red-black-tree';

export class TreeMultiMapNode<
K = any,
Expand Down
75 changes: 31 additions & 44 deletions test/unit/data-structures/binary-tree/bst.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('BST operations test', () => {
[10, 10],
[5, 5]
];
bst.addMany(idsAndValues, undefined, false);
bst.addMany(idsAndValues, [], false);
expect(bst.root).toBeInstanceOf(BSTNode);

if (bst.root) expect(bst.root.key).toBe(11);
Expand Down Expand Up @@ -1538,57 +1538,52 @@ describe('BST iterative methods not map mode test', () => {
});

describe('classic use', () => {
// Test case for finding the kth smallest element
it('@example Find kth smallest element', () => {
// Create a BST with some elements
const bst = new BST<number>([5, 3, 7, 1, 4, 6, 8]);
const sortedKeys = bst.dfs(node => node.key, 'IN');

// Helper function to find kth smallest
const findKthSmallest = (k: number): number | undefined => {
return sortedKeys[k - 1];
};
it('@example Merge 3 sorted datasets', () => {
const dataset1 = new BST<number, string>([
[1, 'A'],
[7, 'G']
]);
const dataset2 = [
[2, 'B'],
[6, 'F']
];
const dataset3 = new BST<number, string>([
[3, 'C'],
[5, 'E'],
[4, 'D']
]);

// Assertions
expect(findKthSmallest(1)).toBe(1);
expect(findKthSmallest(3)).toBe(4);
expect(findKthSmallest(7)).toBe(8);
// Merge datasets into a single BinarySearchTree
const merged = new BST<number, string>(dataset1);
merged.addMany(dataset2);
merged.merge(dataset3);

// Verify merged dataset is in sorted order
expect([...merged.values()]).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G']);
});

// Test case for finding elements in a given range
it('@example Find elements in a range', () => {
const bst = new BST<number>([10, 5, 15, 3, 7, 12, 18]);
expect(bst.search(new Range(5, 10))).toEqual([10, 5, 7]);
expect(bst.rangeSearch([4, 12], false, node => node.key.toString())).toEqual(['10', '12', '5', '7']);
expect(bst.rangeSearch([4, 12], node => node.key.toString())).toEqual(['10', '12', '5', '7']);
expect(bst.search(new Range(4, 12, true, false))).toEqual([10, 5, 7]);
expect(bst.rangeSearch([15, 20])).toEqual([15, 18]);
expect(bst.search(new Range(15, 20, false))).toEqual([18]);
});

// Test case for Huffman coding simulation
it('Huffman coding frequency simulation', () => {
// Create a BST to simulate Huffman tree
const frequencyBST = new BST<string, number>([
['a', 5],
['b', 9],
['c', 12],
['d', 13],
['e', 16],
['f', 45]
]);

// Sort nodes by frequency
const sortedFrequencies = frequencyBST.dfs(node => ({ char: node.key, freq: node.value }), 'IN');

// Build Huffman tree simulation
expect(sortedFrequencies[0].char).toBe('a');
expect(sortedFrequencies[5].char).toBe('f');
});

// Test case for Lowest Common Ancestor (LCA)
it('@example Find lowest common ancestor', () => {
const bst = new BST<number>([20, 10, 30, 5, 15, 25, 35, 3, 7, 12, 18]);

// LCA helper function
const findLCA = (num1: number, num2: number): number | undefined => {
const path1 = bst.getPathToRoot(num1);
const path2 = bst.getPathToRoot(num2);
// Find the first common ancestor
return findFirstCommon(path1, path2);
};

function findFirstCommon(arr1: number[], arr2: number[]): number | undefined {
for (const num of arr1) {
if (arr2.indexOf(num) !== -1) {
Expand All @@ -1598,14 +1593,6 @@ describe('classic use', () => {
return undefined;
}

// LCA helper function
const findLCA = (num1: number, num2: number): number | undefined => {
const path1 = bst.getPathToRoot(num1);
const path2 = bst.getPathToRoot(num2);
// Find the first common ancestor
return findFirstCommon(path1, path2);
};

// Assertions
expect(findLCA(3, 10)).toBe(7);
expect(findLCA(5, 35)).toBe(15);
Expand Down
Loading

0 comments on commit 1494ffa

Please sign in to comment.