feat: All binary tree data structures support the leaves method. It is used to find all leaf nodes.

This commit is contained in:
Revone 2024-10-20 15:40:02 +13:00
parent ea0d4272df
commit 85b79ff1ad
6 changed files with 307 additions and 5 deletions

View file

@ -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<K, V, NODE>`.
* @returns a boolean value.
*/
isNIL(node: R | BTNKeyOrNodeOrEntry<K, V, NODE>) {
isNIL(node: R | BTNKeyOrNodeOrEntry<K, V, NODE>): 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<K, V, NODE>} node - The `node` parameter in the `isLeaf` function
* can be either a regular node (`R`) or a `BTNKeyOrNodeOrEntry<K, V, NODE>`.
* @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<K, V, NODE>): 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<NODE | null>`, where `NODE` is the
* type of nodes in the tree. The default value for `callback` is `
* @param {R | BTNKeyOrNodeOrEntry<K, V, NODE>} 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<C extends BTNCallback<NODE | null>>(
callback: C = this._DEFAULT_CALLBACK as C,
beginRoot: R | BTNKeyOrNodeOrEntry<K, V, NODE> = this.root
): ReturnType<C>[] {
beginRoot = this.ensureNode(beginRoot);
const leaves: ReturnType<BTNCallback<NODE>> = [];
this._collectLeaves(callback, beginRoot, leaves);
return leaves;
}
listLevels<C extends BTNCallback<NODE>>(
callback?: C,
beginRoot?: R | BTNKeyOrNodeOrEntry<K, V, NODE>,
@ -1915,9 +1962,11 @@ export class BinaryTree<
const display = (root: OptBTNOrNull<NODE>): 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<BTNCallback<NODE>>[]} 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<C extends BTNCallback<NODE | null>>(
callback: C = this._DEFAULT_CALLBACK as C,
node: OptBTNOrNull<NODE>,
leaves: ReturnType<BTNCallback<NODE>>[]
): 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);
}
}

View file

@ -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]);
});
});

View file

@ -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]);

View file

@ -956,9 +956,15 @@ describe('BST iterative methods test', () => {
let bst: BST<number, string>;
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']);
});
});

View file

@ -677,3 +677,126 @@ describe('RedBlackTree 2', () => {
});
});
});
describe('RedBlackTree - _deleteFixup', () => {
let tree: RedBlackTree<number, number>;
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<number, number>();
// 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']
]);
});
});

View file

@ -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]);
});
});