mirror of
https://github.com/zrwusa/data-structure-typed.git
synced 2024-11-27 06:24:05 +00:00
docs: more example code for RedBlackTree and BinarySearchTree
This commit is contained in:
parent
4298ac658b
commit
1494ffa84f
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue