diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index d0a2089..d202183 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -26,6 +26,7 @@ import { IBinaryTree } from '../../interfaces'; import { trampoline } from '../../utils'; import { Queue } from '../queue'; import { IterableEntryBase } from '../base'; +import * as console from 'console'; /** * Represents a node in a binary tree. @@ -348,10 +349,26 @@ export class BinaryTree< * `BTNKeyOrNodeOrEntry`. * @returns a boolean value. */ - isNIL(node: R | BTNKeyOrNodeOrEntry) { + isNIL(node: R | BTNKeyOrNodeOrEntry): boolean { return node === this.NIL; } + /** + * The function `isLeaf` determines whether a given node is a leaf node in a binary tree structure. + * @param {R | BTNKeyOrNodeOrEntry} node - The `node` parameter in the `isLeaf` function + * can be either a regular node (`R`) or a `BTNKeyOrNodeOrEntry`. + * @returns The `isLeaf` function is checking if the provided node is a leaf node in a binary tree. + * If the node is `undefined`, it returns `false`. If the node is `null`, it returns `true`. + * Otherwise, it checks if both the left and right children of the node are not real nodes, and + * returns `true` if they are not, indicating that the node is a + */ + isLeaf(node: R | BTNKeyOrNodeOrEntry): boolean { + node = this.ensureNode(node); + if (node === undefined) return false; + if (node === null) return true; + return !this.isRealNode(node.left) && !this.isRealNode(node.right); + } + /** * The function checks if the input is an array with two elements, indicating it is a binary tree * node entry. @@ -1586,6 +1603,36 @@ export class BinaryTree< return ans; } + /** + * Time complexity: O(n) + * Space complexity: O(n) + */ + + /** + * Time complexity: O(n) + * Space complexity: O(n) + * + * The `leaves` function collects and returns the leaves of a binary tree using a specified callback. + * @param {C} callback - The `callback` parameter is a function that will be called for each node in + * the tree. It is a generic type `C` that extends `BTNCallback`, where `NODE` is the + * type of nodes in the tree. The default value for `callback` is ` + * @param {R | BTNKeyOrNodeOrEntry} beginRoot - The `beginRoot` parameter in the `leaves` + * method is used to specify the starting point for collecting leaves in a binary tree. It can be + * either a root node of the tree or a key-value pair or an entry that will be converted to a node. + * If not provided, the default + * @returns The `leaves` method is returning an array of values that are the return type of the + * callback function provided as an argument. + */ + leaves>( + callback: C = this._DEFAULT_CALLBACK as C, + beginRoot: R | BTNKeyOrNodeOrEntry = this.root + ): ReturnType[] { + beginRoot = this.ensureNode(beginRoot); + const leaves: ReturnType> = []; + this._collectLeaves(callback, beginRoot, leaves); + return leaves; + } + listLevels>( callback?: C, beginRoot?: R | BTNKeyOrNodeOrEntry, @@ -1915,9 +1962,11 @@ export class BinaryTree< const display = (root: OptBTNOrNull): void => { const [lines, , ,] = this._displayAux(root, opts); + let paragraph = ''; for (const line of lines) { - console.log(line); + paragraph += line + '\n'; } + console.log(paragraph); }; display(beginRoot); @@ -2187,4 +2236,48 @@ export class BinaryTree< return callback; } + + /** + * Time complexity: O(n) + * Space complexity: O(n) + */ + + /** + * Time complexity: O(n) + * Space complexity: O(n) + * + * The function `_collectLeaves` recursively collects leaf nodes in a binary tree using a specified + * callback function. + * @param {C} callback - The `callback` parameter in the `_collectLeaves` method is a function that + * takes a node (of type `NODE` or `null`) as an argument and returns a value. It is a generic type + * `C` that extends the `BTNCallback` type. The default value for ` + * @param node - The `node` parameter in the `_collectLeaves` method represents a binary tree node. + * It can either be a valid node of type `NODE` or `null`. The method checks if the node is a leaf + * node (i.e., it has no left or right children) and collects the + * @param {ReturnType>[]} leaves - The `leaves` parameter in the `_collectLeaves` + * method is an array that stores the return values of the callback function for each leaf node + * encountered during the traversal of the binary tree. It accumulates these values as leaf nodes are + * visited in the tree. + * @returns In the provided code snippet, the method `_collectLeaves` is a recursive function that + * collects leaf nodes in a binary tree. The function takes a callback function `callback`, a node + * `node`, and an array `leaves` as parameters. + */ + protected _collectLeaves>( + callback: C = this._DEFAULT_CALLBACK as C, + node: OptBTNOrNull, + leaves: ReturnType>[] + ): void { + if (!this.isRealNode(node)) { + return; + } + + // If both left and right are NIL, it's a leaf node + if (this.isLeaf(node)) { + leaves.push(callback(node)); + } + + // Recurse for both left and right children + this._collectLeaves(callback, node.left, leaves); + this._collectLeaves(callback, node.right, leaves); + } } diff --git a/test/unit/data-structures/binary-tree/avl-tree.test.ts b/test/unit/data-structures/binary-tree/avl-tree.test.ts index c972585..5e3d5e3 100644 --- a/test/unit/data-structures/binary-tree/avl-tree.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree.test.ts @@ -411,4 +411,9 @@ describe('AVLTree iterative methods test', () => { const values = avl.values(); expect([...values]).toEqual(['a', 'b', 'c']); }); + + it('should leaves', () => { + const leaves = avl.leaves(); + expect(leaves).toEqual([1, 3]); + }); }); diff --git a/test/unit/data-structures/binary-tree/binary-tree.test.ts b/test/unit/data-structures/binary-tree/binary-tree.test.ts index 901ad69..3900305 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -838,6 +838,11 @@ describe('BinaryTree iterative methods test', () => { expect([...values]).toEqual(['b', 'a', 'c']); }); + it('should leaves', () => { + const leaves = binaryTree.leaves(); + expect(leaves).toEqual([2, 3]); + }); + it('should iterative method return undefined when the node is null', () => { const tree = new BinaryTree(); tree.addMany([-10, -10, -10, 9, 9, 20, null, null, 15, 7, 8, null, 2, null, 6, null, null, 8, 8, 8]); diff --git a/test/unit/data-structures/binary-tree/bst.test.ts b/test/unit/data-structures/binary-tree/bst.test.ts index 4be0786..cdfbc3f 100644 --- a/test/unit/data-structures/binary-tree/bst.test.ts +++ b/test/unit/data-structures/binary-tree/bst.test.ts @@ -956,9 +956,15 @@ describe('BST iterative methods test', () => { let bst: BST; beforeEach(() => { bst = new BST(); - bst.add([1, 'a']); - bst.add([2, 'b']); - bst.add([3, 'c']); + bst.addMany( + [ + [1, 'a'], + [2, 'b'], + [3, 'c'] + ], + [], + false + ); }); it('The node obtained by get Node should match the node type', () => { @@ -1032,4 +1038,69 @@ describe('BST iterative methods test', () => { const values = bst.values(); expect([...values]).toEqual(['a', 'b', 'c']); }); + + it('should leaves', () => { + const leaves = bst.leaves(); + expect(leaves).toEqual([3]); + }); + + it('should collapsed, unbalanced, balanced bst leaves', () => { + const collapsedToLinkedList = new BST(); + collapsedToLinkedList.addMany( + [ + [1, 'a'], + [2, 'b'], + [3, 'c'], + [4, 'd'], + [5, 'e'], + [6, 'f'], + [7, 'g'], + [8, 'h'], + [9, 'i'] + ], + [], + false + ); + + expect(collapsedToLinkedList.leaves()).toEqual([9]); + + const unbalanced = new BST(); + unbalanced.addMany( + [ + [2, 'b'], + [1, 'a'], + [3, 'c'], + [4, 'd'], + [5, 'e'], + [6, 'f'], + [7, 'g'], + [8, 'h'], + [9, 'i'] + ], + [], + false + ); + + expect(unbalanced.leaves()).toEqual([1, 9]); + + const balanced = new BST(); + balanced.addMany( + [ + [2, 'b'], + [1, 'a'], + [3, 'c'], + [4, 'd'], + [5, 'e'], + [6, 'f'], + [7, 'g'], + [8, 'h'], + [9, 'i'] + ], + [], + true + ); + + expect(balanced.leaves()).toEqual([1, 4, 6, 9]); + expect(balanced.leaves(node => node?.value)).toEqual(['a', 'd', 'f', 'i']); + }); }); diff --git a/test/unit/data-structures/binary-tree/rb-tree.test.ts b/test/unit/data-structures/binary-tree/rb-tree.test.ts index 3019fdc..9880b7f 100644 --- a/test/unit/data-structures/binary-tree/rb-tree.test.ts +++ b/test/unit/data-structures/binary-tree/rb-tree.test.ts @@ -677,3 +677,126 @@ describe('RedBlackTree 2', () => { }); }); }); + +describe('RedBlackTree - _deleteFixup', () => { + let tree: RedBlackTree; + + beforeEach(() => { + tree = new RedBlackTree(); + }); + + it('should handle deleting a red leaf node', () => { + tree.add(10, 10); + tree.add(5, 5); // Red leaf + tree.add(20, 20); + + expect(tree.delete(5)).toHaveLength(1); // Delete red leaf + expect(tree.root?.left).toBe(tree.NIL); // Left child should be NIL + }); + + it('should handle deleting a black leaf node', () => { + tree.add(10, 10); + tree.add(5, 5); // Black node + tree.add(20, 20); + tree.add(1, 1); // Black leaf node + + expect(tree.delete(1)).toHaveLength(1); // Delete black leaf + expect(tree.root?.left?.left).toBe(tree.NIL); + }); + + it('should handle deleting black node with red sibling', () => { + tree.add(10, 10); + tree.add(5, 5); // Black node + tree.add(20, 20); // Red sibling + tree.add(25, 25); // Force the sibling to be red + + expect(tree.delete(5)).toHaveLength(1); // Delete black node + expect(tree.root?.right?.color).toBe('BLACK'); // Ensure sibling color is black after fixup + }); + + it('should handle deleting black node with black sibling', () => { + tree.add(10, 10); + tree.add(5, 5); // Black node + tree.add(20, 20); // Black sibling + + expect(tree.delete(5)).toHaveLength(1); // Delete black node + expect(tree.root?.left).toBe(tree.NIL); + }); + + it('should handle deleting the root node', () => { + tree.add(10, 10); // Root node + tree.add(5, 5); + tree.add(20, 20); + + expect(tree.delete(10)).toHaveLength(1); // Delete root node + expect(tree.root?.key).toBe(20); // New root should be 20 + }); + + it('should handle complex case with multiple rotations', () => { + tree.add(10, 10); + tree.add(5, 5); + tree.add(15, 15); + tree.add(12, 12); + tree.add(18, 18); + tree.add(16, 16); + + // Delete a node that will cause rotations and color changes + expect(tree.delete(5)).toHaveLength(1); + + // Verify the color and structure after fixup + expect(tree.root?.color).toBe('BLACK'); + expect(tree.root?.left).toBe(tree.NIL); + expect(tree.root?.right?.left?.color).toBe('BLACK'); + }); + + it('should handle complex delete fixup scenarios', () => { + const tree = new RedBlackTree(); + + // Build a tree that will require complex fixup + tree.add(20, 20); + tree.add(10, 10); + tree.add(30, 30); + tree.add(5, 5); + tree.add(15, 15); + tree.add(25, 25); + tree.add(35, 35); + tree.add(2, 2); + tree.add(8, 8); + + // This deletion should trigger a complex fixup + tree.delete(2); + // tree.print(tree.root, { isShowNull: true, isShowRedBlackNIL: true, isShowUndefined: false }); + + expect(tree.isLeaf(2)).toBe(false); + expect(tree.isLeaf(8)).toBe(true); + expect(tree.isLeaf(15)).toBe(true); + expect(tree.isLeaf(25)).toBe(true); + expect(tree.isLeaf(35)).toBe(true); + expect(tree.isLeaf(20)).toBe(false); + expect(tree.isLeaf(30)).toBe(false); + // Verify tree structure and colors after fixup + expect(tree.root?.color).toBe('BLACK'); + expect(tree.root?.key).toBe(20); + expect(tree.root?.left?.color).toBe('RED'); + expect(tree.root?.left?.key).toBe(10); + expect(tree.root?.right?.color).toBe('BLACK'); + expect(tree.root?.right?.key).toBe(30); + expect(tree.root?.left?.left?.color).toBe('BLACK'); + expect(tree.root?.left?.left?.key).toBe(5); + expect(tree.root?.left?.right?.color).toBe('BLACK'); + expect(tree.root?.left?.right?.key).toBe(15); + expect(tree.leaves(node => (node === null ? '' : `${node.key} ${node.color}`))).toEqual([ + '8 RED', + '15 BLACK', + '25 RED', + '35 RED' + ]); + expect(tree.listLevels(node => (node === tree.NIL ? 'NIL' : `${node.key} ${node.color}`))).toEqual([ + ['20 BLACK'], + ['10 RED', '30 BLACK'], + ['5 BLACK', '15 BLACK', '25 RED', '35 RED'], + ['NIL', '8 RED', 'NIL', 'NIL', 'NIL', 'NIL', 'NIL', 'NIL'], + ['NIL', 'NIL'] + ]); + }); +}); diff --git a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts index 215c4a7..a333efb 100644 --- a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +++ b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts @@ -829,4 +829,9 @@ describe('TreeMultiMap iterative methods test', () => { const values = treeMM.values(); expect([...values]).toEqual(['a', 'b', 'c']); }); + + it('should leaves', () => { + const leaves = treeMM.leaves(); + expect(leaves).toEqual([1, 3]); + }); });