mirror of
https://github.com/zrwusa/data-structure-typed.git
synced 2024-11-23 04:44:04 +00:00
feat: All binary tree data structures support the leaves method. It is used to find all leaf nodes.
This commit is contained in:
parent
ea0d4272df
commit
85b79ff1ad
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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']);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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']
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue