From 3983ab089c10adb97053258a937816c597c8a8c1 Mon Sep 17 00:00:00 2001 From: Revone Date: Mon, 23 Oct 2023 22:36:49 +0800 Subject: [PATCH] [binary-tree] Each method that traverses based on the root node can specify any node within the tree as the root node. --- src/data-structures/binary-tree/avl-tree.ts | 16 +++--- .../binary-tree/binary-tree.ts | 18 ++++--- src/data-structures/binary-tree/bst.ts | 16 +++--- .../binary-tree/tree-multiset.ts | 2 +- test/config.ts | 1 + .../binary-tree/binary-tree.test.ts | 50 +++++++++++++++++++ .../data-structures/binary-tree/bst.test.ts | 9 +++- .../binary-tree/tree-multiset.test.ts | 3 +- test/utils/big-o.ts | 3 +- 9 files changed, 91 insertions(+), 27 deletions(-) create mode 100644 test/config.ts diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts index f5d5aca..3081725 100644 --- a/src/data-structures/binary-tree/avl-tree.ts +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -72,27 +72,25 @@ export class AVLTree = AVLTreeNode> extends B /** * The function overrides the add method of a binary tree node and balances the tree after inserting a new node. - * @param {BinaryTreeNodeKey} key - The `key` parameter is the identifier of the binary tree node that we want to add. + * @param keyOrNode - The `keyOrNode` parameter is either a key or a node that needs to be added to the binary tree. * @param [val] - The `val` parameter is an optional value that can be assigned to the node being added. It is of type * `N['val']`, which means it should be of the same type as the `val` property of the nodes in the binary tree. * @returns The method is returning the inserted node, or null or undefined if the insertion was not successful. */ - override add(key: BinaryTreeNodeKey, val?: N['val']): N | null | undefined { + override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: N['val']): N | null | undefined { // TODO support node as a param - const inserted = super.add(key, val); + const inserted = super.add(keyOrNode, val); if (inserted) this._balancePath(inserted); return inserted; } /** - * The function overrides the delete method of a binary tree and performs additional operations to balance the tree after - * deletion. - * @param {BinaryTreeNodeKey} key - The `key` parameter represents the identifier of the binary tree node that needs to be - * removed. + * The function overrides the delete method of a binary tree and performs additional operations to balance the tree after deletion. * @returns The method is returning an array of `BinaryTreeDeletedResult` objects. + * @param nodeOrKey - The `nodeOrKey` parameter is either a node or a key that needs to be deleted from the binary tree. */ - override delete(key: BinaryTreeNodeKey): BinaryTreeDeletedResult[] { - const deletedResults = super.delete(key); + override delete(nodeOrKey: N | BinaryTreeNodeKey): BinaryTreeDeletedResult[] { + const deletedResults = super.delete(nodeOrKey); for (const {needBalanced} of deletedResults) { if (needBalanced) { this._balancePath(needBalanced); diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index 3c58236..515e526 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -646,7 +646,7 @@ export class BinaryTree = BinaryTreeNode> * @param {N | null} subTreeRoot - The `node` parameter represents the root node of a binary search tree (BST). * @returns a boolean value. */ - isSubtreeBST(subTreeRoot: N | null): boolean { + isSubtreeBST(subTreeRoot: N): boolean { // TODO there is a bug if (!subTreeRoot) return true; @@ -681,6 +681,7 @@ export class BinaryTree = BinaryTreeNode> * @returns The `isBST()` function is returning a boolean value. */ isBST(): boolean { + if (this.root === null) return true; return this.isSubtreeBST(this.root); } @@ -814,7 +815,7 @@ export class BinaryTree = BinaryTreeNode> bfs( callback: BFSCallback = this._defaultCallbackByKey, withLevel: boolean = false, - node?: N | null + node: N | null = this.root ): BFSCallbackReturn[] { if (!node) node = this.root; if (!node) return []; @@ -870,18 +871,23 @@ export class BinaryTree = BinaryTreeNode> /** * The `morris` function performs an in-order, pre-order, or post-order traversal on a binary tree using the Morris traversal algorithm. + * The Morris algorithm only modifies the tree's structure during traversal; once the traversal is complete, + * the tree's structure should be restored to its original state to maintain the tree's integrity. + * This is because the purpose of the Morris algorithm is to save space rather than permanently alter the tree's shape. * @param {'in' | 'pre' | 'post'} [pattern] - The traversal pattern: 'in' (in-order), 'pre' (pre-order), or 'post' (post-order). * @param callback - The `callback` parameter is a function that takes a node as a parameter and returns a value. + * @param beginRoot - The `beginRoot` parameter is an optional parameter of type `N` or `null`. It represents the * @returns An array of BinaryTreeNodeProperties objects. */ morris( callback: MapCallback = this._defaultCallbackByKey, - pattern: DFSOrderPattern = 'in' + pattern: DFSOrderPattern = 'in', + beginRoot: N | null = this.root ): MapCallbackReturn[] { - if (this.root === null) return []; + if (beginRoot === null) return []; const ans: MapCallbackReturn[] = []; - let cur: N | null | undefined = this.root; + let cur: N | null | undefined = beginRoot; const _reverseEdge = (node: N | null | undefined) => { let pre: N | null | undefined = null; let next: N | null | undefined = null; @@ -952,7 +958,7 @@ export class BinaryTree = BinaryTreeNode> } cur = cur.right; } - _printEdge(this.root); + _printEdge(beginRoot); break; } return ans; diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index e5ae80c..eb245f0 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -227,10 +227,10 @@ export class BST = BSTNode> extends BinaryTree * the values at index 0 and 1 is less than, otherwise it returns the key of the leftmost node. If the comparison is * equal, it returns the key of the rightmost node. If there are no nodes in the tree, it returns 0. */ - lastKey(): BinaryTreeNodeKey { - if (this._compare(0, 1) === CP.lt) return this.getRightMost()?.key ?? 0; - else if (this._compare(0, 1) === CP.gt) return this.getLeftMost()?.key ?? 0; - else return this.getRightMost()?.key ?? 0; + lastKey(beginRoot: N | null = this.root): BinaryTreeNodeKey { + if (this._compare(0, 1) === CP.lt) return this.getRightMost(beginRoot)?.key ?? 0; + else if (this._compare(0, 1) === CP.gt) return this.getLeftMost(beginRoot)?.key ?? 0; + else return this.getRightMost(beginRoot)?.key ?? 0; } /** @@ -312,13 +312,13 @@ export class BST = BSTNode> extends BinaryTree lesserOrGreaterTraverse( callback: MapCallback = this._defaultCallbackByKey, lesserOrGreater: CP = CP.lt, - node: N | BinaryTreeNodeKey | null + node: N | BinaryTreeNodeKey | null = this.root ): MapCallbackReturn { if (typeof node === 'number') node = this.get(node); const ans: MapCallbackReturn[] = []; - if (!node) return []; + if (!node) return ans; const key = node.key; - if (!this.root) return false; + if (!this.root) return ans; if (this.loopType === LoopType.RECURSIVE) { const _traverse = (cur: N) => { @@ -331,7 +331,7 @@ export class BST = BSTNode> extends BinaryTree }; _traverse(this.root); - return true; + return ans; } else { const queue = new Queue([this.root]); while (queue.size > 0) { diff --git a/src/data-structures/binary-tree/tree-multiset.ts b/src/data-structures/binary-tree/tree-multiset.ts index c2ea9fa..b3fcbb1 100644 --- a/src/data-structures/binary-tree/tree-multiset.ts +++ b/src/data-structures/binary-tree/tree-multiset.ts @@ -293,7 +293,7 @@ export class TreeMultiset = TreeMultiset * not be taken into account when removing it. If `ignoreCount` is set to `false * @returns The function `delete` returns an array of `BinaryTreeDeletedResult` objects. */ - override delete(nodeOrKey: N | BinaryTreeNodeKey, ignoreCount = false): BinaryTreeDeletedResult[] { + override delete(nodeOrKey: N | BinaryTreeNodeKey, ignoreCount = false): BinaryTreeDeletedResult[]{ const bstDeletedResult: BinaryTreeDeletedResult[] = []; if (!this.root) return bstDeletedResult; diff --git a/test/config.ts b/test/config.ts new file mode 100644 index 0000000..2316e5e --- /dev/null +++ b/test/config.ts @@ -0,0 +1 @@ +export const isDebugTest = true; 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 acd8368..21cbe99 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -144,3 +144,53 @@ describe('BinaryTree', () => { expect(binaryTree.root).toBeNull(); }); }); + +describe('BinaryTree Morris Traversal', () => { + // Create a binary tree + const tree = new BinaryTree>(); + tree.add(1); + tree.add(2); + tree.add(3); + tree.add(4); + tree.add(5); + it('should perform in-order Morris traversal correctly as dfs traversal', () => { + // Perform in-order Morris traversal + const result = tree.morris(node => node.key, 'in'); + + // Expected in-order traversal result + const expected = [4, 2, 5, 1, 3]; + + expect(result).toEqual(expected); + expect(tree.dfs(node => node.key, 'in')).toEqual(expected); + }); + + it('should perform pre-order Morris traversal correctly as dfs traversal', () => { + // Perform pre-order Morris traversal + const result = tree.morris(node => node.key, 'pre'); + + // Expected pre-order traversal result + const expected = [1, 2, 4, 5, 3]; + + expect(result).toEqual(expected); + expect(tree.dfs(node => node.key, 'pre')).toEqual(expected); + }); + + it('should perform post-order Morris traversal correctly as dfs traversal', () => { + // Perform post-order Morris traversal + const result = tree.morris(node => node.key, 'post'); + + // Expected post-order traversal result + const expected = [4, 5, 2, 3, 1]; + + expect(result).toEqual([4, 5, 2, 3, 1]); + expect(tree.dfs(node => node.key, 'post')).toEqual(expected); + }); + + it('after morris traversals should the structure of the tree be correct', () => { + const node1 = tree.get(1); + const node2 = tree.get(2); + const node3 = tree.get(3); + expect(node1?.left).toBe(node2); + expect(node1?.right).toBe(node3); + }); +}); diff --git a/test/unit/data-structures/binary-tree/bst.test.ts b/test/unit/data-structures/binary-tree/bst.test.ts index 75d4e2d..9683af9 100644 --- a/test/unit/data-structures/binary-tree/bst.test.ts +++ b/test/unit/data-structures/binary-tree/bst.test.ts @@ -1,6 +1,7 @@ import {BST, BSTNode, CP} from '../../../../src'; +import {isDebugTest} from '../../../config'; -const isDebug = true; +const isDebug = isDebugTest; describe('BST operations test', () => { it('should perform various operations on a Binary Search Tree with numeric values', () => { @@ -444,4 +445,10 @@ describe('BST Performance test', function () { isDebug && console.log('---listLevels', arr); isDebug && console.log('---listLevels', performance.now() - startL); }); + + it('should the lastKey of a BST to be the largest key', function () { + const bst = new BST(); + bst.addMany([9, 8, 7, 3, 1, 2, 5, 4, 6], undefined, false); + expect(bst.lastKey()).toBe(9); + }); }); diff --git a/test/unit/data-structures/binary-tree/tree-multiset.test.ts b/test/unit/data-structures/binary-tree/tree-multiset.test.ts index 4a9a101..6812143 100644 --- a/test/unit/data-structures/binary-tree/tree-multiset.test.ts +++ b/test/unit/data-structures/binary-tree/tree-multiset.test.ts @@ -1,6 +1,7 @@ import {CP, TreeMultiset, TreeMultisetNode} from '../../../../src'; +import {isDebugTest} from '../../../config'; -const isDebug = false; +const isDebug = isDebugTest; describe('TreeMultiset operations test', () => { it('should perform various operations on a Binary Search Tree with numeric values', () => { const treeMultiset = new TreeMultiset(); diff --git a/test/utils/big-o.ts b/test/utils/big-o.ts index e9082a8..43d7eca 100644 --- a/test/utils/big-o.ts +++ b/test/utils/big-o.ts @@ -1,6 +1,7 @@ import {AnyFunction} from '../types'; +import {isDebugTest} from '../config'; -const isDebug = false; +const isDebug = isDebugTest; const orderReducedBy = 2; // reduction of bigO's order compared to the baseline bigO export const magnitude = {