docs: more example code for RedBlackTree and BinarySearchTree

This commit is contained in:
Revone 2024-11-24 17:37:33 +13:00
parent 4298ac658b
commit 1494ffa84f
7 changed files with 154 additions and 156 deletions

View file

@ -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",

View file

@ -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) {
@ -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
@ -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
@ -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);
}
/**

View file

@ -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';

View file

@ -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,

View file

@ -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,

View file

@ -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);
@ -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');
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']
]);
// 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
expect(findKthSmallest(1)).toBe(1);
expect(findKthSmallest(3)).toBe(4);
expect(findKthSmallest(7)).toBe(8);
// 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) {
@ -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);

View file

@ -821,100 +821,55 @@ describe('RedBlackTree - _deleteFixup', () => {
});
describe('classic use', () => {
it('Database Index: Add, Search, and Delete Records', () => {
const dbIndex = new RedBlackTree<number, string>();
// Insert records
dbIndex.add(1, 'Alice');
dbIndex.add(2, 'Bob');
dbIndex.add(3, 'Charlie');
// Search for records
expect(dbIndex.get(1)).toBe('Alice');
expect(dbIndex.get(2)).toBe('Bob');
expect(dbIndex.get(3)).toBe('Charlie');
// Delete a record
dbIndex.delete(2);
expect(dbIndex.get(2)).toBeUndefined();
});
it('@example Merge 3 sorted datasets', () => {
const dataset1 = new RedBlackTree<number, string>([
[1, 'A'],
[7, 'G']
]);
const dataset2 = [
[2, 'B'],
[6, 'F']
];
const dataset3 = new RedBlackTree<number, string>([
[3, 'C'],
[5, 'E'],
[4, 'D']
]);
// Merge datasets into a single Red-Black Tree
const merged = new RedBlackTree<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('Find elements in a range', () => {
it('@example Find elements in a range', () => {
const bst = new RedBlackTree<number>([10, 5, 15, 3, 7, 12, 18]);
expect(bst.search(new Range(5, 10))).toEqual([5, 10, 7]);
expect(bst.search(new Range(4, 12))).toEqual([5, 10, 12, 7]);
expect(bst.search(new Range(15, 20))).toEqual([15, 18]);
});
it('Timer List: Manage Timed Tasks', () => {
const timerList = new RedBlackTree<number, string>(); // Key: Time in ms, Value: Task Name
it('@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
}
// Schedule tasks
timerList.add(100, 'Task A');
timerList.add(200, 'Task B');
timerList.add(50, 'Task C');
// 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 }
];
// Verify the order of tasks by retrieval
expect([...timerList.values()]).toEqual(['Task C', 'Task A', 'Task B']); // Sorted by key (time)
// Extend the stock record type to include metadata for database usage
type StockTableRecord = StockRecord & { lastUpdated: Date };
// Remove the earliest task
timerList.delete(50);
expect([...timerList.values()]).toEqual(['Task A', 'Task B']);
});
// 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
}
]
});
it('Scheduler: Manage Tasks by Priority', () => {
const scheduler = new RedBlackTree<number, string>(); // Key: Priority, Value: Task Name
// Query the stock with the highest price
const highestPricedStock = priceIndex.getRightMost();
expect(priceIndex.get(highestPricedStock)?.symbol).toBe('AMZN'); // Amazon has the highest price
// Add tasks with different priorities
scheduler.add(3, 'Low Priority Task');
scheduler.add(1, 'High Priority Task');
scheduler.add(2, 'Medium Priority Task');
// Verify the order of tasks by retrieval
expect([...scheduler.values()]).toEqual(['High Priority Task', 'Medium Priority Task', 'Low Priority Task']);
// Remove the highest priority task
scheduler.delete(1);
expect([...scheduler.values()]).toEqual(['Medium Priority Task', 'Low Priority Task']);
});
it('Routing Table: Manage IP Routes', () => {
const routingTable = new RedBlackTree<number, string>(); // Key: IP Address, Value: Route
// Add routes
routingTable.add(1921680101, 'Route A');
routingTable.add(1921680102, 'Route B');
routingTable.add(1921680100, 'Route C');
// Search for a specific route
expect(routingTable.get(1921680101)).toBe('Route A');
// Verify all routes in sorted order
expect([...routingTable.values()]).toEqual(['Route C', 'Route A', 'Route B']);
// 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
);
expect(stocksInRange).toEqual(['GOOGL', 'MSFT', 'META']); // Verify stocks in the range
});
});