From 1a893cfd202c3c3df9417e7c2a0f8d07b85a60f0 Mon Sep 17 00:00:00 2001 From: Revone Date: Tue, 29 Aug 2023 17:29:57 +0800 Subject: [PATCH] After extracting the count member variable to TreeMultiset, the initial test has passed successfully. --- .../binary-tree/abstract-binary-tree.ts | 191 ++---- src/data-structures/binary-tree/avl-tree.ts | 8 +- .../binary-tree/binary-tree.ts | 4 +- src/data-structures/binary-tree/bst.ts | 51 +- src/data-structures/binary-tree/rb-tree.ts | 9 +- .../binary-tree/tree-multiset.ts | 604 ++++++++++++++++++ .../interfaces/abstract-binary-tree.ts | 10 +- src/data-structures/interfaces/avl-tree.ts | 2 +- src/data-structures/interfaces/bst.ts | 4 +- .../types/abstract-binary-tree.ts | 3 +- src/utils/types/validate-type.ts | 6 +- src/utils/utils.ts | 12 +- .../binary-tree/avl-tree.test.ts | 16 +- .../data-structures/binary-tree/bst.test.ts | 34 +- .../binary-tree/tree-multiset.test.ts | 405 ++++++++++++ 15 files changed, 1127 insertions(+), 232 deletions(-) create mode 100644 tests/unit/data-structures/binary-tree/tree-multiset.test.ts diff --git a/src/data-structures/binary-tree/abstract-binary-tree.ts b/src/data-structures/binary-tree/abstract-binary-tree.ts index 4c9fc89..fffa6e3 100644 --- a/src/data-structures/binary-tree/abstract-binary-tree.ts +++ b/src/data-structures/binary-tree/abstract-binary-tree.ts @@ -31,10 +31,9 @@ export abstract class AbstractBinaryTreeNode 1 && !ignoreCount) { - curr.count--; - this._setCount(this.count - 1); - } else { - if (!curr.left) { - if (!parent) { - if (curr.right !== undefined) this._setRoot(curr.right); - } else { - const {familyPosition: fp} = curr; - if (fp === FamilyPosition.LEFT || fp === FamilyPosition.ROOT_LEFT) { - parent.left = curr.right; - } else if (fp === FamilyPosition.RIGHT || fp === FamilyPosition.ROOT_RIGHT) { - parent.right = curr.right; - } - needBalanced = parent; - } + + if (!curr.left) { + if (!parent) { + if (curr.right !== undefined) this._setRoot(curr.right); } else { - const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : null; - if (leftSubTreeRightMost) { - const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; - orgCurrent = this.swapLocation(curr, leftSubTreeRightMost); - if (parentOfLeftSubTreeMax) { - if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; - else parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; - needBalanced = parentOfLeftSubTreeMax; - } + const {familyPosition: fp} = curr; + if (fp === FamilyPosition.LEFT || fp === FamilyPosition.ROOT_LEFT) { + parent.left = curr.right; + } else if (fp === FamilyPosition.RIGHT || fp === FamilyPosition.ROOT_RIGHT) { + parent.right = curr.right; + } + needBalanced = parent; + } + } else { + const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : null; + if (leftSubTreeRightMost) { + const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; + orgCurrent = this.swapLocation(curr, leftSubTreeRightMost); + if (parentOfLeftSubTreeMax) { + if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; + else parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; + needBalanced = parentOfLeftSubTreeMax; } } - this._setSize(this.size - 1); - this._setCount(this.count - orgCurrent.count); } + this._setSize(this.size - 1); + bstDeletedResult.push({deleted: orgCurrent, needBalanced}); return bstDeletedResult; @@ -543,30 +515,28 @@ export abstract class AbstractBinaryTree = new Map(); - - while (stack.length > 0 || node) { - if (node) { - stack.push(node); - node = node.left; - } else { - node = stack[stack.length - 1] - if (!node.right || last === node.right) { - node = stack.pop(); - if (node) { - const leftHeight = node.left ? depths.get(node.left) ?? -1 : -1; - const rightHeight = node.right ? depths.get(node.right) ?? -1 : -1; - depths.set(node, 1 + Math.max(leftHeight, rightHeight)); - last = node; - node = null; - } - } else node = node.right - } + if (!beginRoot) { + return -1; } - return depths.get(beginRoot) ?? -1; + const stack: { node: N; depth: number }[] = [{node: beginRoot, depth: 0}]; + let maxHeight = 0; + + while (stack.length > 0) { + const {node, depth} = stack.pop()!; + + if (node.left) { + stack.push({node: node.left, depth: depth + 1}); + } + + if (node.right) { + stack.push({node: node.right, depth: depth + 1}); + } + + maxHeight = Math.max(maxHeight, depth); + } + + return maxHeight; } } @@ -626,7 +596,7 @@ export abstract class AbstractBinaryTree= this.getHeight(beginRoot)); } @@ -648,7 +618,7 @@ export abstract class AbstractBinaryTree { if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return; if (!cur.left && !cur.right) return; @@ -682,6 +652,7 @@ export abstract class AbstractBinaryTree 0; } @@ -843,32 +814,30 @@ export abstract class AbstractBinaryTree { - res[0]++; - res[1] += cur.count; + size++; cur.left && _traverse(cur.left); cur.right && _traverse(cur.right); } _traverse(subTreeRoot); - return res; + return size; } else { const stack: N[] = [subTreeRoot]; while (stack.length > 0) { const cur = stack.pop()!; - res[0]++; - res[1] += cur.count; + size++; cur.right && stack.push(cur.right); cur.left && stack.push(cur.left); } - return res; + return size; } } @@ -898,9 +867,6 @@ export abstract class AbstractBinaryTree { if (!levelsNodes[level]) levelsNodes[level] = []; collectByProperty(node, level); @@ -1278,8 +1227,6 @@ export abstract class AbstractBinaryTree = AVLTreeNode> extends B super(options); } - override createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N { - return new AVLTreeNode(id, val, count) as N; + override createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new AVLTreeNode(id, val) as N; } /** @@ -33,8 +33,8 @@ export class AVLTree = AVLTreeNode> extends B * to `1`, indicating that the value should be inserted once. * @returns The method is returning either an N object or null. */ - override add(id: BinaryTreeNodeId, val?: N['val'], count?: number): N | null { - const inserted = super.add(id, val, count); + override add(id: BinaryTreeNodeId, val?: N['val']): N | null | undefined { + const inserted = super.add(id, val); if (inserted) this.balancePath(inserted); return inserted; } diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index c55d832..6b780e2 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -38,8 +38,8 @@ export class BinaryTree = BinaryTreeNode> * of occurrences of the value in the binary tree node. If not provided, the default value is `undefined`. * @returns a BinaryTreeNode object if the value is not null, otherwise it returns null. */ - createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N { - return new BinaryTreeNode(id, val, count) as N; + createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new BinaryTreeNode(id, val) as N; } } \ No newline at end of file diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index 9892391..684e4f1 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -38,8 +38,8 @@ export class BST = BSTNode> extends BinaryTree * of a particular value in the binary search tree node. * @returns a new instance of the BSTNode class, casted as type N. */ - override createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N { - return new BSTNode(id, val, count) as N; + override createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new BSTNode(id, val) as N; } /** @@ -54,13 +54,12 @@ export class BST = BSTNode> extends BinaryTree * inserted once. * @returns The method `add` returns a `N` object or `null`. */ - override add(id: BinaryTreeNodeId, val?: N['val'], count: number = 1): N | null { + override add(id: BinaryTreeNodeId, val?: N['val']): N | null | undefined { let inserted: N | null = null; - const newNode = this.createNode(id, val, count); + const newNode = this.createNode(id, val); if (this.root === null) { this._setRoot(newNode); this._setSize(this.size + 1); - this._setCount(this.count + count); inserted = (this.root); } else { let cur = this.root; @@ -69,8 +68,6 @@ export class BST = BSTNode> extends BinaryTree if (cur !== null && newNode !== null) { if (this._compare(cur.id, id) === CP.eq) { if (newNode) { - cur.count += newNode.count; - this._setCount(this.count + newNode.count); cur.val = newNode.val; } //Duplicates are not accepted. @@ -85,7 +82,6 @@ export class BST = BSTNode> extends BinaryTree //Add to the left of the current node cur.left = newNode; this._setSize(this.size + 1); - this._setCount(this.count + newNode.count); traversing = false; inserted = cur.left; } else { @@ -101,7 +97,6 @@ export class BST = BSTNode> extends BinaryTree //Add to the right of the current node cur.right = newNode; this._setSize(this.size + 1); - this._setCount(this.count + newNode.count); traversing = false; inserted = (cur.right); } else { @@ -220,9 +215,6 @@ export class BST = BSTNode> extends BinaryTree case 'id': needSum = cur.id; break; - case 'count': - needSum = cur.count; - break; default: needSum = cur.id; break; @@ -299,23 +291,19 @@ export class BST = BSTNode> extends BinaryTree case 'id': cur.id += delta; break; - case 'count': - cur.count += delta; - break; default: cur.id += delta; break; } } - if (this.loopType === LoopType.RECURSIVE) { const _traverse = (cur: N) => { const compared = this._compare(cur.id, id); - _sumByPropertyName(cur); + if (compared === CP.gt) _sumByPropertyName(cur); if (!cur.left && !cur.right) return; - if (cur.left && compared === CP.gt) _traverse(cur.left); - else if (cur.right && compared === CP.gt) _traverse(cur.right); + if (cur.left && this._compare(cur.left.id, id) === CP.gt) _traverse(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) _traverse(cur.right); }; _traverse(this.root); @@ -325,23 +313,34 @@ export class BST = BSTNode> extends BinaryTree while (queue.length > 0) { const cur = queue.shift(); if (cur) { - const compared = this._compare(cur.id, node.id); - _sumByPropertyName(cur); + const compared = this._compare(cur.id, id); + if (compared === CP.gt) _sumByPropertyName(cur); - if (cur.left && compared === CP.gt) queue.push(cur.left); - else if (cur.right && compared === CP.gt) queue.push(cur.right); + if (cur.left && this._compare(cur.left.id, id) === CP.gt) queue.push(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) queue.push(cur.right); } } return true; } } + /** + * Balancing Adjustment: + * Perfectly Balanced Binary Tree: Since the balance of a perfectly balanced binary tree is already fixed, no additional balancing adjustment is needed. Any insertion or deletion operation will disrupt the perfect balance, often requiring a complete reconstruction of the tree. + * AVL Tree: After insertion or deletion operations, an AVL tree performs rotation adjustments based on the balance factor of nodes to restore the tree's balance. These rotations can be left rotations, right rotations, left-right rotations, or right-left rotations, performed as needed. + * + * Use Cases and Efficiency: + * Perfectly Balanced Binary Tree: Perfectly balanced binary trees are typically used in specific scenarios such as complete binary heaps in heap sort or certain types of Huffman trees. However, they are not suitable for dynamic operations requiring frequent insertions and deletions, as these operations often necessitate full tree reconstruction. + * AVL Tree: AVL trees are well-suited for scenarios involving frequent searching, insertion, and deletion operations. Through rotation adjustments, AVL trees maintain their balance, ensuring average and worst-case time complexity of O(log n). + */ + + /** * The `balance` function takes a sorted array of nodes and builds a balanced binary search tree using either a * recursive or iterative approach. * @returns The `balance()` function returns a boolean value. */ - balance(): boolean { + perfectlyBalance(): boolean { const sorted = this.DFS('in', 'node'), n = sorted.length; this.clear(); @@ -351,7 +350,7 @@ export class BST = BSTNode> extends BinaryTree if (l > r) return; const m = l + Math.floor((r - l) / 2); const midNode = sorted[m]; - this.add(midNode.id, midNode.val, midNode.count); + this.add(midNode.id, midNode.val); buildBalanceBST(l, m - 1); buildBalanceBST(m + 1, r); }; @@ -367,7 +366,7 @@ export class BST = BSTNode> extends BinaryTree if (l <= r) { const m = l + Math.floor((r - l) / 2); const midNode = sorted[m]; - this.add(midNode.id, midNode.val, midNode.count); + this.add(midNode.id, midNode.val); stack.push([m + 1, r]); stack.push([l, m - 1]); } diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/rb-tree.ts index 9d04a87..960dc95 100644 --- a/src/data-structures/binary-tree/rb-tree.ts +++ b/src/data-structures/binary-tree/rb-tree.ts @@ -4,8 +4,9 @@ import {BST, BSTNode} from './bst'; export class RBTreeNode = RBTreeNodeNested> extends BSTNode implements IRBTreeNode { - constructor(id: BinaryTreeNodeId, val?: T, count?: number) { - super(id, val, count); + constructor(id: BinaryTreeNodeId, color: RBColor, val?: T) { + super(id, val); + this._color = color; } private _color: RBColor = RBColor.RED; @@ -58,8 +59,8 @@ export class RBTree = RBTreeNode> extends BST< super(options); } - override createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N { - return new RBTreeNode(id, val, count) as N; + override createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new RBTreeNode(id, RBColor.RED, val) as N; } // private override _root: BinaryTreeNode | null = null; diff --git a/src/data-structures/binary-tree/tree-multiset.ts b/src/data-structures/binary-tree/tree-multiset.ts index a7d98e2..43d2735 100644 --- a/src/data-structures/binary-tree/tree-multiset.ts +++ b/src/data-structures/binary-tree/tree-multiset.ts @@ -6,10 +6,26 @@ * @license MIT License */ import type {BinaryTreeNodeId, TreeMultisetNodeNested, TreeMultisetOptions} from '../types'; +import {BinaryTreeDeletedResult, CP, DFSOrderPattern, FamilyPosition, LoopType, NodeOrPropertyName} from '../types'; import {ITreeMultiset, ITreeMultisetNode} from '../interfaces'; import {AVLTree, AVLTreeNode} from './avl-tree'; +import {ObjectWithNumberId} from '../../utils'; export class TreeMultisetNode = TreeMultisetNodeNested> extends AVLTreeNode implements ITreeMultisetNode { + constructor(id: BinaryTreeNodeId, val?: T, count: number = 1) { + super(id, val); + this._count = count; + } + + private _count = 1; + + get count(): number { + return this._count; + } + + set count(v: number) { + this._count = v; + } } /** @@ -20,6 +36,12 @@ export class TreeMultiset = TreeMultiset super({...options, isMergeDuplicatedVal: true}); } + private _count = 0; + + get count(): number { + return this._count; + } + /** * The function creates a new BSTNode with the given id, value, and count. * @param {BinaryTreeNodeId} id - The id parameter is the unique identifier for the binary tree node. It is used to @@ -32,4 +54,586 @@ export class TreeMultiset = TreeMultiset override createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N { return new TreeMultisetNode(id, val, count) as N; } + + override swapLocation(srcNode: N, destNode: N): N { + const {val, count, height, id} = destNode; + const tempNode = this.createNode(id, val, count); + if (tempNode) { + tempNode.height = height; + + if (tempNode instanceof TreeMultisetNode) { + // TODO should we consider the left, right children? + + + destNode.id = srcNode.id; + destNode.val = srcNode.val; + destNode.count = srcNode.count; + destNode.height = srcNode.height; + + srcNode.id = tempNode.id; + srcNode.val = tempNode.val; + srcNode.count = tempNode.count; + srcNode.height = tempNode.height; + } + } + + return destNode; + } + + /** + * The `add` function inserts a new node with a given ID and value into a binary tree, updating the count if the node + * already exists. + * @param {BinaryTreeNodeId} id - The id parameter is the identifier of the binary tree node. It is used to uniquely + * identify each node in the binary tree. + * @param {N} val - The value to be inserted into the binary tree. + * @param {number} [count] - The `count` parameter is an optional parameter that specifies the number of times the + * value should be inserted into the binary tree. If not provided, it defaults to 1. + * @returns The function `add` returns a `N` object if a new node is inserted, or `null` if no new node + * is inserted, or `undefined` if the insertion fails. + */ + // override add(id: BinaryTreeNodeId, val?: N['val'], count?: number): N | null | undefined { + // count = count ?? 1; + // + // const _bfs = (root: N, newNode: N | null): N | undefined | null => { + // const queue: Array = [root]; + // while (queue.length > 0) { + // const cur = queue.shift(); + // if (cur) { + // const inserted = this.addTo(newNode, cur); + // if (inserted !== undefined) return inserted; + // if (cur.left) queue.push(cur.left); + // if (cur.right) queue.push(cur.right); + // } else return; + // } + // return; + // }; + // + // let inserted: N | null | undefined; + // const needInsert = val !== null ? this.createNode(id, val, count) : null; + // const existNode = val !== null ? this.get(id, 'id') : null; + // if (this.root) { + // if (existNode) { + // existNode.count += count; + // existNode.val = val ?? id; + // if (needInsert !== null) { + // this._setCount(this.count + count); + // inserted = existNode; + // } + // } else { + // inserted = _bfs(this.root, needInsert); + // } + // } else { + // this._setRoot(val !== null ? this.createNode(id, val, count) : null); + // if (needInsert !== null) { + // this._setSize(1); + // this._setCount(count); + // } + // inserted = this.root; + // } + // return inserted; + // } + + override add(id: BinaryTreeNodeId, val?: N['val'], count?: number): N | null | undefined { + count = count ?? 1; + let inserted: N | null = null; + const newNode = this.createNode(id, val, count); + if (this.root === null) { + this._setRoot(newNode); + this._setSize(this.size + 1); + this._setCount(this.count + count); + inserted = (this.root); + } else { + let cur = this.root; + let traversing = true; + while (traversing) { + if (cur !== null && newNode !== null) { + if (this._compare(cur.id, id) === CP.eq) { + if (newNode) { + cur.val = newNode.val; + cur.count += count; + this._setCount(this.count + newNode.count); + } + //Duplicates are not accepted. + traversing = false; + inserted = cur; + } else if (this._compare(cur.id, id) === CP.gt) { + // Traverse left of the node + if (cur.left === undefined) { + if (newNode) { + newNode.parent = cur; + } + //Add to the left of the current node + cur.left = newNode; + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count); + + traversing = false; + inserted = cur.left; + } else { + //Traverse the left of the current node + if (cur.left) cur = cur.left; + } + } else if (this._compare(cur.id, id) === CP.lt) { + // Traverse right of the node + if (cur.right === undefined) { + if (newNode) { + newNode.parent = cur; + } + //Add to the right of the current node + cur.right = newNode; + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count); + + traversing = false; + inserted = (cur.right); + } else { + //Traverse the left of the current node + if (cur.right) cur = cur.right; + } + } + } else { + traversing = false; + } + } + } + if (inserted) this.balancePath(inserted); + return inserted; + } + + /** + * The function adds a new node to a binary tree as the left or right child of a given parent node. + * @param {N | null} newNode - The `newNode` parameter represents the node that you want to add to the tree. It can be + * either a node object (`N`) or `null`. + * @param {N} parent - The `parent` parameter represents the parent node to which the new node will be added as a + * child. + * @returns either the left or right child node that was added to the parent node. It can also return `null` or + * `undefined` in certain cases. + */ + override addTo(newNode: N | null, parent: N): N | null | undefined { + if (parent) { + if (parent.left === undefined) { + if (newNode) { + newNode.parent = parent; + } + parent.left = newNode; + if (newNode !== null) { + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count ?? 0) + } + + return parent.left; + } else if (parent.right === undefined) { + if (newNode) { + newNode.parent = parent; + } + parent.right = newNode; + if (newNode !== null) { + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count ?? 0); + } + return parent.right; + } else { + return; + } + } else { + return; + } + } + + /** + * The `addMany` function inserts multiple items into a binary tree and returns an array of the inserted nodes or + * null/undefined values. + * @param {N[] | N[]} data - The `data` parameter can be either an array of elements of type `N` or an + * array of `N` objects. + * @returns The function `addMany` returns an array of `N`, `null`, or `undefined` values. + */ + override addMany(data: N[] | Array): (N | null | undefined)[] { + // TODO not sure addMany not be run multi times + const inserted: (N | null | undefined)[] = []; + const map: Map = new Map(); + + if (this.isMergeDuplicatedVal) { + for (const nodeOrId of data) map.set(nodeOrId, (map.get(nodeOrId) ?? 0) + 1); + } + + for (const nodeOrId of data) { + + if (nodeOrId instanceof TreeMultisetNode) { + inserted.push(this.add(nodeOrId.id, nodeOrId.val, nodeOrId.count)); + continue; + } + + if (nodeOrId === null) { + inserted.push(this.add(NaN, null, 0)); + continue; + } + + + // TODO will this cause an issue? + const count = this.isMergeDuplicatedVal ? map.get(nodeOrId) : 1; + let newId: BinaryTreeNodeId; + if (typeof nodeOrId === 'number') { + newId = this.autoIncrementId ? this.maxId + 1 : nodeOrId; + } else if (nodeOrId instanceof Object) { + if (this.autoIncrementId) { + newId = this.maxId + 1; + } else { + if (Object.keys(nodeOrId).includes('id')) { + newId = (nodeOrId as ObjectWithNumberId).id; + } else { + console.warn(nodeOrId, 'Object value must has an id property when the autoIncrementId is false'); + continue; + } + } + } else { + console.warn(nodeOrId, ` is not added`); + continue; + } + + if (this.isMergeDuplicatedVal) { + if (map.has(nodeOrId)) { + inserted.push(this.add(newId, nodeOrId, count)); + map.delete(nodeOrId); + } + } else { + inserted.push(this.add(newId, nodeOrId, 1)); + } + + this._setMaxId(newId); + } + return inserted; + } + + /** + * The `remove` function removes a node from a binary search tree and returns the deleted node along with the parent + * node that needs to be balanced. + * @param {N | BinaryTreeNodeId | null} nodeOrId - The `nodeOrId` parameter can be one of the following: + * @param {boolean} [ignoreCount] - The `ignoreCount` parameter is an optional boolean parameter that determines + * whether to ignore the count of the node being removed. If `ignoreCount` is set to `true`, the count of the node will + * not be taken into account when removing it. If `ignoreCount` is set to `false + * @returns The function `remove` returns an array of `BinaryTreeDeletedResult` objects. + */ + override remove(nodeOrId: N | BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] { + const bstDeletedResult: BinaryTreeDeletedResult[] = []; + if (!this.root) return bstDeletedResult; + + const curr: N | null = (typeof nodeOrId === 'number') ? this.get(nodeOrId) : nodeOrId; + if (!curr) return bstDeletedResult; + + const parent: N | null = curr?.parent ? curr.parent : null; + let needBalanced: N | null = null, orgCurrent = curr; + + if (curr.count > 1 && !ignoreCount) { + curr.count--; + this._setCount(this.count - 1); + } else { + if (!curr.left) { + if (!parent) { + if (curr.right !== undefined) this._setRoot(curr.right); + } else { + const {familyPosition: fp} = curr; + if (fp === FamilyPosition.LEFT || fp === FamilyPosition.ROOT_LEFT) { + parent.left = curr.right; + } else if (fp === FamilyPosition.RIGHT || fp === FamilyPosition.ROOT_RIGHT) { + parent.right = curr.right; + } + needBalanced = parent; + } + } else { + const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : null; + if (leftSubTreeRightMost) { + const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; + orgCurrent = this.swapLocation(curr, leftSubTreeRightMost); + if (parentOfLeftSubTreeMax) { + if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; + else parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; + needBalanced = parentOfLeftSubTreeMax; + } + } + } + this._setSize(this.size - 1); + this._setCount(this.count - orgCurrent.count); + } + + bstDeletedResult.push({deleted: orgCurrent, needBalanced}); + + if (needBalanced) { + this.balancePath(needBalanced); + } + + return bstDeletedResult; + } + + /** + * The function calculates the size and count of a subtree in a binary tree using either recursive or iterative + * traversal. + * @param {N | null | undefined} subTreeRoot - The `subTreeRoot` parameter is the root node of a binary + * tree. + * @returns The function `getSubTreeSizeAndCount` returns an array `[number, number]`. The first element of the array + * represents the size of the subtree, and the second element represents the count of the nodes in the subtree. + */ + getSubTreeCount(subTreeRoot: N | null | undefined) { + const res: [number, number] = [0, 0]; + if (!subTreeRoot) return res; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + res[0]++; + res[1] += cur.count; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + return res; + } else { + const stack: N[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + res[0]++; + res[1] += cur.count; + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + + return res; + } + } + + subTreeSumCount(subTreeRoot: N | BinaryTreeNodeId | null): number { + + if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); + + if (!subTreeRoot) return 0; + + let sum = 0; + + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N): void => { + sum += cur.count; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + } else { + const stack: N[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + sum += cur.count; + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + } + + return sum; + } + + /** + * The function `subTreeAddCount` recursively or iteratively traverses a binary tree and adds a given delta value to + * the `count` property of each node. + * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree + * in a binary tree. It can be either a `BinaryTreeNodeId` (a unique identifier for a node in the binary tree), a + * `BinaryTreeNode` object, or `null` if the subtree is empty. + * @param {number} delta - The delta parameter is a number that represents the amount by which the count of each node + * in the subtree should be increased or decreased. + * @returns a boolean value. + */ + subTreeAddCount(subTreeRoot: N | BinaryTreeNodeId | null, delta: number): boolean { + if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); + + if (!subTreeRoot) return false; + + const _addByProperty = (cur: N) => { + cur.count += delta; + this._setCount(this.count + delta); + } + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + _addByProperty(cur); + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + }; + + _traverse(subTreeRoot); + } else { + const stack: N[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + + _addByProperty(cur); + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + } + return true; + } + + getNodesByCount(nodeProperty: BinaryTreeNodeId | N, onlyOne ?: boolean): N[] { + if (!this.root) return []; + const result: N[] = []; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + if (cur.count === nodeProperty) { + result.push(cur); + if (onlyOne) return; + } + + if (!cur.left && !cur.right) return; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(this.root); + } else { + const queue: N[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + if (cur.count === nodeProperty) { + result.push(cur); + if (onlyOne) return result; + } + + cur.left && queue.push(cur.left); + cur.right && queue.push(cur.right); + + } + } + } + + return result; + } + + BFSCount(): number[] { + const nodes = super.BFS('node'); + return nodes.map(node => node.count); + } + + listLevelsCount(node: N | null): number[][] { + const levels = super.listLevels(node, 'node'); + return levels.map(level => level.map(node => node.count)); + } + + morrisCount(pattern?: 'in' | 'pre' | 'post'): number[] { + pattern = pattern || 'in'; + const nodes = super.morris(pattern, 'node'); + return nodes.map(node => node.count); + } + + DFSIterativeCount(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): number[] { + pattern = pattern ?? 'in'; + const nodes = super.DFSIterative(pattern, 'node'); + return nodes.map(node => node.count); + } + + DFSCount(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[] { + pattern = pattern ?? 'in'; + const nodes = super.DFS(pattern, 'node'); + return nodes.map(node => node.count); + + } + + lesserSumCount(beginNode: N | BinaryTreeNodeId | null): number { + if (typeof beginNode === 'number') beginNode = this.get(beginNode, 'id'); + if (!beginNode) return 0; + if (!this.root) return 0; + const id = beginNode.id; + + let sum = 0; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N): void => { + const compared = this._compare(cur.id, id); + if (compared === CP.eq) { + if (cur.right) sum += this.subTreeSumCount(cur.right); + return; + } else if (compared === CP.lt) { + if (cur.left) sum += this.subTreeSumCount(cur.left); + sum += cur.count; + if (cur.right) _traverse(cur.right); + else return; + } else { + if (cur.left) _traverse(cur.left); + else return; + } + }; + + _traverse(this.root); + } else { + const queue: N[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + const compared = this._compare(cur.id, id); + if (compared === CP.eq) { + if (cur.right) sum += this.subTreeSumCount(cur.right); + return sum; + } else if (compared === CP.lt) { // todo maybe a bug + if (cur.left) sum += this.subTreeSumCount(cur.left); + sum += cur.count; + if (cur.right) queue.push(cur.right); + else return sum; + } else { + if (cur.left) queue.push(cur.left); + else return sum; + } + } + } + } + + return sum; + } + + allGreaterNodesAddCount(node: N | BinaryTreeNodeId | null, delta: number): boolean { + if (typeof node === 'number') node = this.get(node, 'id'); + if (!node) return false; + const id = node.id; + if (!this.root) return false; + + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + const compared = this._compare(cur.id, id); + if (compared === CP.gt) cur.count += delta; + + if (!cur.left && !cur.right) return; + if (cur.left && this._compare(cur.left.id, id) === CP.gt) _traverse(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) _traverse(cur.right); + }; + + _traverse(this.root); + return true; + } else { + const queue: N[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + const compared = this._compare(cur.id, id); + if (compared === CP.gt) cur.count += delta + + if (cur.left && this._compare(cur.left.id, id) === CP.gt) queue.push(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) queue.push(cur.right); + } + } + return true; + } + } + + override clear() { + this._setRoot(null); + this._setSize(0); + this._setCount(0); + this._setMaxId(-1); + } + + protected _setCount(v: number) { + this._count = v; + } } diff --git a/src/data-structures/interfaces/abstract-binary-tree.ts b/src/data-structures/interfaces/abstract-binary-tree.ts index 62315f7..6ec2d45 100644 --- a/src/data-structures/interfaces/abstract-binary-tree.ts +++ b/src/data-structures/interfaces/abstract-binary-tree.ts @@ -35,10 +35,6 @@ export interface IAbstractBinaryTreeNode> extends export interface IAVLTree> extends IBST { - add(id: BinaryTreeNodeId, val?: N['val'] | null, count?: number): N | null + add(id: BinaryTreeNodeId, val?: N['val'] | null): N | null | undefined remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): BinaryTreeDeletedResult[] diff --git a/src/data-structures/interfaces/bst.ts b/src/data-structures/interfaces/bst.ts index 824c9bd..fe61950 100644 --- a/src/data-structures/interfaces/bst.ts +++ b/src/data-structures/interfaces/bst.ts @@ -8,7 +8,7 @@ export interface IBSTNode> extends IBinary export interface IBST> extends IBinaryTree { createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N - add(id: BinaryTreeNodeId, val?: N['val'] | null, count?: number): N | null + add(id: BinaryTreeNodeId, val?: N['val'] | null, count?: number): N | null | undefined get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null @@ -24,7 +24,7 @@ export interface IBST> extends IBinaryTree { allGreaterNodesAdd(node: N, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean - balance(): boolean + perfectlyBalance(): boolean isAVLBalanced(): boolean diff --git a/src/data-structures/types/abstract-binary-tree.ts b/src/data-structures/types/abstract-binary-tree.ts index 00ed7da..54ab2e6 100644 --- a/src/data-structures/types/abstract-binary-tree.ts +++ b/src/data-structures/types/abstract-binary-tree.ts @@ -19,11 +19,12 @@ export enum FamilyPosition { MAL_NODE = 'MAL_NODE' } -export type BinaryTreeNodePropertyName = 'id' | 'val' | 'count'; +export type BinaryTreeNodePropertyName = 'id' | 'val'; export type NodeOrPropertyName = 'node' | BinaryTreeNodePropertyName; export type DFSOrderPattern = 'in' | 'pre' | 'post'; export type BinaryTreeNodeId = number; export type BinaryTreeDeletedResult = { deleted: N | null | undefined, needBalanced: N | null }; +export type AVLTreeDeletedResult = { deleted: N | null | undefined }; export type AbstractBinaryTreeNodeProperty> = N['val'] diff --git a/src/utils/types/validate-type.ts b/src/utils/types/validate-type.ts index d448b9e..e3be25d 100644 --- a/src/utils/types/validate-type.ts +++ b/src/utils/types/validate-type.ts @@ -16,6 +16,10 @@ export type ObjectWithNumberId = { id: number; } -export type RestrictValById = NonNumberNonObjectButDefined | ObjectWithoutId | ObjectWithNonNumberId | ObjectWithNumberId; +export type RestrictValById = + NonNumberNonObjectButDefined + | ObjectWithoutId + | ObjectWithNonNumberId + | ObjectWithNumberId; export type DummyAny = string | number | boolean | null | undefined | object | symbol | void | Function | never; \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 50187f3..bf28bff 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,3 @@ -import {z} from 'zod' /** * data-structure-typed * @@ -6,16 +5,7 @@ import {z} from 'zod' * @copyright Copyright (c) 2022 Tyler Zeng * @license MIT License */ -import type { - NonNumberNonObjectButDefined, - ObjectWithNonNumberId, - ObjectWithNumberId, - ObjectWithoutId, - Thunk, - ToThunkFn, - TrlAsyncFn, - TrlFn -} from './types'; +import type {Thunk, ToThunkFn, TrlAsyncFn, TrlFn} from './types'; export const uuidV4 = function () { return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c) { diff --git a/tests/unit/data-structures/binary-tree/avl-tree.test.ts b/tests/unit/data-structures/binary-tree/avl-tree.test.ts index 7d5aea9..fc4e8d7 100644 --- a/tests/unit/data-structures/binary-tree/avl-tree.test.ts +++ b/tests/unit/data-structures/binary-tree/avl-tree.test.ts @@ -16,8 +16,6 @@ describe('AVL Tree Test', () => { const getNodeById = tree.get(10, 'id'); expect(getNodeById?.id).toBe(10); - const getNodesByCount = tree.getNodes(1, 'count'); - expect(getNodesByCount.length).toBe(16); const getMinNodeByRoot = tree.getLeftMost(); expect(getMinNodeByRoot?.id).toBe(1); @@ -32,26 +30,20 @@ describe('AVL Tree Test', () => { const lesserSum = tree.lesserSum(10); expect(lesserSum).toBe(45); - if (node15) { - const subTreeAdd = tree.subTreeAdd(node15, 1, 'count'); - expect(subTreeAdd).toBe(true); - } + // node15 has type problem. After the uniform design, the generics of containers (DirectedGraph, BST) are based on the type of value. However, this design has a drawback: when I attempt to inherit from the Vertex or BSTNode classes, the types of the results obtained by all methods are those of the parent class. expect(node15?.val).toBe(15); const node11 = tree.get(11); - if (node11) { - const allGreaterNodesAdd = tree.allGreaterNodesAdd(node11, 2, 'count'); - expect(allGreaterNodesAdd).toBe(true); - } + const dfs = tree.DFS('in', 'node'); expect(dfs[0].id).toBe(1); expect(dfs[dfs.length - 1].id).toBe(16); - tree.balance(); + tree.perfectlyBalance(); const bfs = tree.BFS('node'); - expect(tree.isBalanced()).toBe(true); + expect(tree.isPerfectlyBalanced()).toBe(true); expect(bfs[0].id).toBe(8); expect(bfs[bfs.length - 1].id).toBe(16); diff --git a/tests/unit/data-structures/binary-tree/bst.test.ts b/tests/unit/data-structures/binary-tree/bst.test.ts index 5f0a7f1..782d451 100644 --- a/tests/unit/data-structures/binary-tree/bst.test.ts +++ b/tests/unit/data-structures/binary-tree/bst.test.ts @@ -12,7 +12,6 @@ describe('BST operations test', () => { if (bst.root) expect(bst.root.id).toBe(11); expect(bst.size).toBe(16); - expect(bst.count).toBe(16); expect(bst.has(6)).toBe(true); @@ -26,8 +25,6 @@ describe('BST operations test', () => { const nodeVal9 = bst.get(9, 'val'); expect(nodeVal9?.id).toBe(9); - const nodesByCount1 = bst.getNodes(1, 'count'); - expect(nodesByCount1.length).toBe(16); const leftMost = bst.getLeftMost(); expect(leftMost?.id).toBe(1); @@ -43,24 +40,16 @@ describe('BST operations test', () => { expect(lesserSum).toBe(45); expect(node15).toBeInstanceOf(BSTNode); - if (node15 instanceof BSTNode) { - const subTreeAdd = bst.subTreeAdd(15, 1, 'count'); - expect(subTreeAdd).toBeDefined(); - } const node11 = bst.get(11); expect(node11).toBeInstanceOf(BSTNode); - if (node11 instanceof BSTNode) { - const allGreaterNodesAdded = bst.allGreaterNodesAdd(11, 2, 'count'); - expect(allGreaterNodesAdded).toBeDefined(); - } const dfsInorderNodes = bst.DFS('in', 'node'); expect(dfsInorderNodes[0].id).toBe(1); expect(dfsInorderNodes[dfsInorderNodes.length - 1].id).toBe(16); - bst.balance(); - expect(bst.isBalanced()).toBe(true); + bst.perfectlyBalance(); + expect(bst.isPerfectlyBalanced()).toBe(true); const bfsNodesAfterBalanced = bst.BFS('node'); expect(bfsNodesAfterBalanced[0].id).toBe(8); @@ -191,7 +180,6 @@ describe('BST operations test', () => { expect(bfsNodes[1].id).toBe(12); expect(bfsNodes[2].id).toBe(16); - expect(bst.count).toBe(5); }); it('should perform various operations on a Binary Search Tree with object values', () => { @@ -211,8 +199,6 @@ describe('BST operations test', () => { if (objBST.root) expect(objBST.root.id).toBe(11); - expect(objBST.count).toBe(16); - expect(objBST.has(6)).toBe(true); const node6 = objBST.get(6); @@ -225,9 +211,6 @@ describe('BST operations test', () => { const nodeVal9 = objBST.get(9, 'id'); expect(nodeVal9?.id).toBe(9); - const nodesByCount1 = objBST.getNodes(1, 'count'); - expect(nodesByCount1.length).toBe(16); - const leftMost = objBST.getLeftMost(); expect(leftMost?.id).toBe(1); @@ -243,24 +226,16 @@ describe('BST operations test', () => { expect(lesserSum).toBe(45); expect(node15).toBeInstanceOf(BSTNode); - if (node15 instanceof BSTNode) { - const subTreeAdd = objBST.subTreeAdd(node15, 1, 'count'); - expect(subTreeAdd).toBeDefined(); - } const node11 = objBST.get(11); expect(node11).toBeInstanceOf(BSTNode); - if (node11 instanceof BSTNode) { - const allGreaterNodesAdded = objBST.allGreaterNodesAdd(node11, 2, 'count'); - expect(allGreaterNodesAdded).toBeDefined(); - } const dfsInorderNodes = objBST.DFS('in', 'node'); expect(dfsInorderNodes[0].id).toBe(1); expect(dfsInorderNodes[dfsInorderNodes.length - 1].id).toBe(16); - objBST.balance(); - expect(objBST.isBalanced()).toBe(true); + objBST.perfectlyBalance(); + expect(objBST.isPerfectlyBalanced()).toBe(true); const bfsNodesAfterBalanced = objBST.BFS('node'); expect(bfsNodesAfterBalanced[0].id).toBe(8); @@ -391,6 +366,5 @@ describe('BST operations test', () => { expect(bfsNodes[1].id).toBe(12); expect(bfsNodes[2].id).toBe(16); - expect(objBST.count).toBe(5); }); }); diff --git a/tests/unit/data-structures/binary-tree/tree-multiset.test.ts b/tests/unit/data-structures/binary-tree/tree-multiset.test.ts new file mode 100644 index 0000000..3263d9f --- /dev/null +++ b/tests/unit/data-structures/binary-tree/tree-multiset.test.ts @@ -0,0 +1,405 @@ +import {TreeMultiset, TreeMultisetNode} from '../../../../src'; + +describe('TreeMultiset operations test', () => { + it('should perform various operations on a Binary Search Tree with numeric values', () => { + const treeMultiset = new TreeMultiset(); + + + expect(treeMultiset instanceof TreeMultiset); + treeMultiset.add(11); + treeMultiset.add(3); + treeMultiset.addMany([11, 3, 15, 1, 8, 13, 16, 2, 6, 9, 12, 14, 4, 7, 10, 5]); + expect(treeMultiset.root instanceof TreeMultisetNode); + + if (treeMultiset.root) expect(treeMultiset.root.id == 11); + + expect(treeMultiset.size === 16); + expect(treeMultiset.count === 18); + expect(treeMultiset.BFS('id')) + + expect(treeMultiset.has(6)); + + expect(treeMultiset.getHeight(6) === 3); + expect(treeMultiset.getDepth(6) === 1); + const nodeId10 = treeMultiset.get(10); + expect(nodeId10?.id === 10); + + const nodeVal9 = treeMultiset.get(9, 'val'); + expect(nodeVal9?.id === 9); + + const nodesByCount1 = treeMultiset.getNodesByCount(1); + expect(nodesByCount1.length === 14); + + const nodesByCount2 = treeMultiset.getNodesByCount(2); + expect(nodesByCount2.length === 2); + const leftMost = treeMultiset.getLeftMost(); + expect(leftMost?.id === 1); + + const node15 = treeMultiset.get(15); + const minNodeBySpecificNode = node15 && treeMultiset.getLeftMost(node15); + expect(minNodeBySpecificNode?.id === 12); + + const subTreeSum = node15 && treeMultiset.subTreeSum(15); + expect(subTreeSum === 70); + const lesserSum = treeMultiset.lesserSum(10); + expect(lesserSum === 45); + + + expect(node15 instanceof TreeMultisetNode); + if (node15 instanceof TreeMultisetNode) { + const subTreeAdd = treeMultiset.subTreeAddCount(15, 1); + expect(subTreeAdd); + } + const node11 = treeMultiset.get(11); + expect(node11 instanceof TreeMultisetNode); + if (node11 instanceof TreeMultisetNode) { + const allGreaterNodesAdded = treeMultiset.allGreaterNodesAddCount(11, 2); + expect(allGreaterNodesAdded); + } + + const dfsInorderNodes = treeMultiset.DFS('in', 'node'); + expect(dfsInorderNodes[0].id === 1); + expect(dfsInorderNodes[dfsInorderNodes.length - 1].id === 16); + expect(treeMultiset.isPerfectlyBalanced() === false); + + treeMultiset.perfectlyBalance(); + + expect(treeMultiset.isPerfectlyBalanced() === true); + expect(treeMultiset.isAVLBalanced() === true); + + const bfsNodesAfterBalanced = treeMultiset.BFS('node'); + expect(bfsNodesAfterBalanced[0].id === 8); + expect(bfsNodesAfterBalanced[bfsNodesAfterBalanced.length - 1].id === 16); + + const removed11 = treeMultiset.remove(11, true); + expect(removed11 instanceof Array); + expect(removed11[0]); + expect(removed11[0].deleted); + + if (removed11[0].deleted) expect(removed11[0].deleted.id === 11); + + expect(treeMultiset.isAVLBalanced() === true); + + expect(treeMultiset.getHeight(15) === 1); + + const removed1 = treeMultiset.remove(1, true); + expect(removed1 instanceof Array); + expect(removed1[0]); + expect(removed1[0].deleted); + if (removed1[0].deleted) expect(removed1[0].deleted.id === 1); + + expect(treeMultiset.isAVLBalanced() === true); + + expect(treeMultiset.getHeight() === 4); + + const removed4 = treeMultiset.remove(4, true); + expect(removed4 instanceof Array); + expect(removed4[0]); + expect(removed4[0].deleted); + if (removed4[0].deleted) expect(removed4[0].deleted.id === 4); + + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 4); + + const removed10 = treeMultiset.remove(10, true); + expect(removed10 instanceof Array); + expect(removed10[0]); + expect(removed10[0].deleted); + if (removed10[0].deleted) expect(removed10[0].deleted.id === 10); + expect(treeMultiset.isAVLBalanced() === true); + + expect(treeMultiset.getHeight() === 3); + + const removed15 = treeMultiset.remove(15, true); + expect(removed15 instanceof Array); + expect(removed15[0]); + expect(removed15[0].deleted); + if (removed15[0].deleted) expect(removed15[0].deleted.id === 15); + + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 3); + + const removed5 = treeMultiset.remove(5, true); + expect(removed5 instanceof Array); + expect(removed5[0]); + expect(removed5[0].deleted); + if (removed5[0].deleted) expect(removed5[0].deleted.id === 5); + + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 3); + + const removed13 = treeMultiset.remove(13, true); + expect(removed13 instanceof Array); + expect(removed13[0]); + expect(removed13[0].deleted); + if (removed13[0].deleted) expect(removed13[0].deleted.id === 13); + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 3); + + const removed3 = treeMultiset.remove(3, true); + expect(removed3 instanceof Array); + expect(removed3[0]); + expect(removed3[0].deleted); + if (removed3[0].deleted) expect(removed3[0].deleted.id === 3); + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 3); + + const removed8 = treeMultiset.remove(8, true); + expect(removed8 instanceof Array); + expect(removed8[0]); + expect(removed8[0].deleted); + if (removed8[0].deleted) expect(removed8[0].deleted.id === 8); + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 3); + + const removed6 = treeMultiset.remove(6, true); + expect(removed6 instanceof Array); + expect(removed6[0]); + expect(removed6[0].deleted); + if (removed6[0].deleted) expect(removed6[0].deleted.id === 6); + expect(treeMultiset.remove(6, true).length === 0); + expect(treeMultiset.isAVLBalanced() === true); + + expect(treeMultiset.getHeight() === 2); + + const removed7 = treeMultiset.remove(7, true); + expect(removed7 instanceof Array); + expect(removed7[0]); + expect(removed7[0].deleted); + if (removed7[0].deleted) expect(removed7[0].deleted.id === 7); + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 2); + + const removed9 = treeMultiset.remove(9, true); + expect(removed9 instanceof Array); + expect(removed9[0]); + expect(removed9[0].deleted); + if (removed9[0].deleted) expect(removed9[0].deleted.id === 9); + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 2); + + const removed14 = treeMultiset.remove(14, true); + expect(removed14 instanceof Array); + expect(removed14[0]); + expect(removed14[0].deleted); + if (removed14[0].deleted) expect(removed14[0].deleted.id === 14); + expect(treeMultiset.isAVLBalanced() === true); + expect(treeMultiset.getHeight() === 1); + + expect(treeMultiset.isAVLBalanced() === true); + + const bfsIDs = treeMultiset.BFS(); + + expect(bfsIDs[0] === 12); + expect(bfsIDs[1] === 2); + expect(bfsIDs[2] === 16); + + const bfsNodes = treeMultiset.BFS('node'); + + expect(bfsNodes[0].id === 12); + expect(bfsNodes[1].id === 2); + expect(bfsNodes[2].id === 16); + + expect(treeMultiset.count === 3); + }); + + it('should perform various operations on a Binary Search Tree with object values', () => { + const objTreeMultiset = new TreeMultiset>(); + expect(objTreeMultiset).toBeInstanceOf(TreeMultiset); + objTreeMultiset.add(11, {id: 11, keyA: 11}); + objTreeMultiset.add(3, {id: 3, keyA: 3}); + const values = [{id: 15, keyA: 15}, {id: 1, keyA: 1}, {id: 8, keyA: 8}, + {id: 13, keyA: 13}, {id: 16, keyA: 16}, {id: 2, keyA: 2}, + {id: 6, keyA: 6}, {id: 9, keyA: 9}, {id: 12, keyA: 12}, + {id: 14, keyA: 14}, {id: 4, keyA: 4}, {id: 7, keyA: 7}, + {id: 10, keyA: 10}, {id: 5, keyA: 5}]; + + objTreeMultiset.addMany(values); + + expect(objTreeMultiset.root).toBeInstanceOf(TreeMultisetNode); + + if (objTreeMultiset.root) expect(objTreeMultiset.root.id).toBe(11); + + expect(objTreeMultiset.count).toBe(16); + + expect(objTreeMultiset.has(6)).toBe(true); + + const node6 = objTreeMultiset.get(6); + // expect(node6 && objTreeMultiset.getHeight(node6)).toBe(2); + // expect(node6 && objTreeMultiset.getDepth(node6)).toBe(3); + // + // const nodeId10 = objTreeMultiset.get(10, 'id'); + // expect(nodeId10?.id).toBe(10); + // + // const nodeVal9 = objTreeMultiset.get(9, 'id'); + // expect(nodeVal9?.id).toBe(9); + // + // const nodesByCount1 = objTreeMultiset.getNodesByCount(1); + // expect(nodesByCount1.length).toBe(16); + // + // const leftMost = objTreeMultiset.getLeftMost(); + // expect(leftMost?.id).toBe(1); + // + // const node15 = objTreeMultiset.get(15); + // expect(node15?.val).toEqual({id: 15, keyA: 15}); + // const minNodeBySpecificNode = node15 && objTreeMultiset.getLeftMost(node15); + // expect(minNodeBySpecificNode?.id).toBe(12); + // + // const subTreeSum = node15 && objTreeMultiset.subTreeSum(node15); + // expect(subTreeSum).toBe(70); + // + // const lesserSum = objTreeMultiset.lesserSum(10); + // expect(lesserSum).toBe(45); + // + // expect(node15).toBeInstanceOf(TreeMultisetNode); + // if (node15 instanceof TreeMultisetNode) { + // const subTreeAdd = objTreeMultiset.subTreeAddCount(node15, 1); + // expect(subTreeAdd).toBeDefined(); + // } + // + // const node11 = objTreeMultiset.get(11); + // expect(node11).toBeInstanceOf(TreeMultisetNode); + // if (node11 instanceof TreeMultisetNode) { + // const allGreaterNodesAdded = objTreeMultiset.allGreaterNodesAddCount(node11, 2); + // expect(allGreaterNodesAdded).toBeDefined(); + // } + // + // const dfsInorderNodes = objTreeMultiset.DFS('in', 'node'); + // expect(dfsInorderNodes[0].id).toBe(1); + // expect(dfsInorderNodes[dfsInorderNodes.length - 1].id).toBe(16); + // + // objTreeMultiset.perfectlyBalance(); + // expect(objTreeMultiset.isPerfectlyBalanced()).toBe(true); + // + // const bfsNodesAfterBalanced = objTreeMultiset.BFS('node'); + // expect(bfsNodesAfterBalanced[0].id).toBe(8); + // expect(bfsNodesAfterBalanced[bfsNodesAfterBalanced.length - 1].id).toBe(16); + // + // const removed11 = objTreeMultiset.remove(11, true); + // expect(removed11).toBeInstanceOf(Array); + // expect(removed11[0]).toBeDefined(); + // expect(removed11[0].deleted).toBeDefined(); + // + // if (removed11[0].deleted) expect(removed11[0].deleted.id).toBe(11); + // + // expect(objTreeMultiset.isAVLBalanced()).toBe(true); + // + // expect(node15 && objTreeMultiset.getHeight(node15)).toBe(2); + // + // const removed1 = objTreeMultiset.remove(1, true); + // expect(removed1).toBeInstanceOf(Array); + // expect(removed1[0]).toBeDefined(); + // expect(removed1[0].deleted).toBeDefined(); + // if (removed1[0].deleted) expect(removed1[0].deleted.id).toBe(1); + // + // expect(objTreeMultiset.isAVLBalanced()).toBe(true); + // + // expect(objTreeMultiset.getHeight()).toBe(4); + // + // const removed4 = objTreeMultiset.remove(4, true); + // expect(removed4).toBeInstanceOf(Array); + // expect(removed4[0]).toBeDefined(); + // expect(removed4[0].deleted).toBeDefined(); + // if (removed4[0].deleted) expect(removed4[0].deleted.id).toBe(4); + // expect(objTreeMultiset.isAVLBalanced()).toBe(true); + // expect(objTreeMultiset.getHeight()).toBe(4); + // + // const removed10 = objTreeMultiset.remove(10, true); + // expect(removed10).toBeInstanceOf(Array); + // expect(removed10[0]).toBeDefined(); + // expect(removed10[0].deleted).toBeDefined(); + // if (removed10[0].deleted) expect(removed10[0].deleted.id).toBe(10); + // expect(objTreeMultiset.isAVLBalanced()).toBe(false); + // expect(objTreeMultiset.getHeight()).toBe(4); + // + // const removed15 = objTreeMultiset.remove(15, true); + // expect(removed15).toBeInstanceOf(Array); + // expect(removed15[0]).toBeDefined(); + // expect(removed15[0].deleted).toBeDefined(); + // if (removed15[0].deleted) expect(removed15[0].deleted.id).toBe(15); + // + // expect(objTreeMultiset.isAVLBalanced()).toBe(true); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed5 = objTreeMultiset.remove(5, true); + // expect(removed5).toBeInstanceOf(Array); + // expect(removed5[0]).toBeDefined(); + // expect(removed5[0].deleted).toBeDefined(); + // if (removed5[0].deleted) expect(removed5[0].deleted.id).toBe(5); + // + // expect(objTreeMultiset.isAVLBalanced()).toBe(true); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed13 = objTreeMultiset.remove(13, true); + // expect(removed13).toBeInstanceOf(Array); + // expect(removed13[0]).toBeDefined(); + // expect(removed13[0].deleted).toBeDefined(); + // if (removed13[0].deleted) expect(removed13[0].deleted.id).toBe(13); + // expect(objTreeMultiset.isAVLBalanced()).toBe(true); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed3 = objTreeMultiset.remove(3, true); + // expect(removed3).toBeInstanceOf(Array); + // expect(removed3[0]).toBeDefined(); + // expect(removed3[0].deleted).toBeDefined(); + // if (removed3[0].deleted) expect(removed3[0].deleted.id).toBe(3); + // expect(objTreeMultiset.isAVLBalanced()).toBe(false); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed8 = objTreeMultiset.remove(8, true); + // expect(removed8).toBeInstanceOf(Array); + // expect(removed8[0]).toBeDefined(); + // expect(removed8[0].deleted).toBeDefined(); + // if (removed8[0].deleted) expect(removed8[0].deleted.id).toBe(8); + // expect(objTreeMultiset.isAVLBalanced()).toBe(true); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed6 = objTreeMultiset.remove(6, true); + // expect(removed6).toBeInstanceOf(Array); + // expect(removed6[0]).toBeDefined(); + // expect(removed6[0].deleted).toBeDefined(); + // if (removed6[0].deleted) expect(removed6[0].deleted.id).toBe(6); + // expect(objTreeMultiset.remove(6, true).length).toBe(0); + // expect(objTreeMultiset.isAVLBalanced()).toBe(false); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed7 = objTreeMultiset.remove(7, true); + // expect(removed7).toBeInstanceOf(Array); + // expect(removed7[0]).toBeDefined(); + // expect(removed7[0].deleted).toBeDefined(); + // if (removed7[0].deleted) expect(removed7[0].deleted.id).toBe(7); + // expect(objTreeMultiset.isAVLBalanced()).toBe(false); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed9 = objTreeMultiset.remove(9, true); + // expect(removed9).toBeInstanceOf(Array); + // expect(removed9[0]).toBeDefined(); + // expect(removed9[0].deleted).toBeDefined(); + // if (removed9[0].deleted) expect(removed9[0].deleted.id).toBe(9); + // expect(objTreeMultiset.isAVLBalanced()).toBe(false); + // expect(objTreeMultiset.getHeight()).toBe(3); + // + // const removed14 = objTreeMultiset.remove(14, true); + // expect(removed14).toBeInstanceOf(Array); + // expect(removed14[0]).toBeDefined(); + // expect(removed14[0].deleted).toBeDefined(); + // if (removed14[0].deleted) expect(removed14[0].deleted.id).toBe(14); + // expect(objTreeMultiset.isAVLBalanced()).toBe(false); + // expect(objTreeMultiset.getHeight()).toBe(2); + // + // + // expect(objTreeMultiset.isAVLBalanced()).toBe(false); + // + // const bfsIDs = objTreeMultiset.BFS(); + // expect(bfsIDs[0]).toBe(2); + // expect(bfsIDs[1]).toBe(12); + // expect(bfsIDs[2]).toBe(16); + // + // const bfsNodes = objTreeMultiset.BFS('node'); + // expect(bfsNodes[0].id).toBe(2); + // expect(bfsNodes[1].id).toBe(12); + // expect(bfsNodes[2].id).toBe(16); + // + // expect(objTreeMultiset.count).toBe(5); + }); +});