diff --git a/CHANGELOG.md b/CHANGELOG.md index bb10201..5029d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. - [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - [`auto-changelog`](https://github.com/CookPete/auto-changelog) -## [v1.41.0](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) +## [v1.41.1](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) ### Changes diff --git a/package-lock.json b/package-lock.json index 3257bd9..51cd0b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "data-structure-typed", - "version": "1.41.0", + "version": "1.41.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "data-structure-typed", - "version": "1.41.0", + "version": "1.41.1", "license": "MIT", "devDependencies": { "@types/benchmark": "^2.1.3", diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index cf8a05b..1feb431 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -559,21 +559,21 @@ export class BinaryTree = BinaryTreeNode has>( identifier: BTNKey, callback?: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): boolean; has>( identifier: N | null, callback?: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): boolean; has>( identifier: ReturnType | null, callback: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): boolean; @@ -601,28 +601,28 @@ export class BinaryTree = BinaryTreeNode iterationType = this.iterationType ): boolean { if ((identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; - // TODO may support finding node by value equal + return this.getNodes(identifier, callback, true, beginRoot, iterationType).length > 0; } getNode>( identifier: BTNKey, callback?: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): N | null; getNode>( identifier: N | null, callback?: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): N | null; getNode>( identifier: ReturnType, callback: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): N | null; @@ -648,28 +648,28 @@ export class BinaryTree = BinaryTreeNode iterationType = this.iterationType ): N | null { if ((identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; - // TODO may support finding node by value equal + return this.getNodes(identifier, callback, true, beginRoot, iterationType)[0] ?? null; } get>( identifier: BTNKey, callback?: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): V | undefined; get>( identifier: N | null, callback?: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): V | undefined; get>( identifier: ReturnType, callback: C, - beginRoot?: N, + beginRoot?: N | null, iterationType?: IterationType ): V | undefined; @@ -695,8 +695,8 @@ export class BinaryTree = BinaryTreeNode iterationType = this.iterationType ): V | undefined { if ((identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; - // TODO may support finding node by value equal - return this.getNodes(identifier, callback, true, beginRoot, iterationType)[0].value ?? undefined; + + return this.getNode(identifier, callback, beginRoot, iterationType)?.value ?? undefined; } /** diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/rb-tree.ts index f256e40..71f1dc9 100644 --- a/src/data-structures/binary-tree/rb-tree.ts +++ b/src/data-structures/binary-tree/rb-tree.ts @@ -1,4 +1,13 @@ +/** + * data-structure-typed + * + * @author Tyler Zeng + * @copyright Copyright (c) 2022 Tyler Zeng + * @license MIT License + */ + import {RBTNColor} from '../../types'; +import * as console from 'console'; export class RBTreeNode { key: number; @@ -16,11 +25,18 @@ export class RBTreeNode { } } -export const SN = new RBTreeNode(0); +export const NIL = new RBTreeNode(0); +/** + * 1. Each node is either red or black. + * 2. The root node is always black. + * 3. Leaf nodes are typically NIL nodes and are considered black. + * 4. Red nodes must have black children. + * 5. Black balance: Every path from any node to each of its leaf nodes contains the same number of black nodes. + */ export class RedBlackTree { constructor() { - this._root = SN; + this._root = NIL; } protected _root: RBTreeNode; @@ -38,13 +54,13 @@ export class RedBlackTree { */ insert(key: number): void { const node: RBTreeNode = new RBTreeNode(key, RBTNColor.RED); - node.left = SN; - node.right = SN; + node.left = NIL; + node.right = NIL; let y: RBTreeNode = null as unknown as RBTreeNode; let x: RBTreeNode = this.root; - while (x !== SN) { + while (x !== NIL) { y = x; if (node.key < x.key) { x = x.left; @@ -83,9 +99,9 @@ export class RedBlackTree { */ delete(key: number): void { const helper = (node: RBTreeNode): void => { - let z: RBTreeNode = SN; + let z: RBTreeNode = NIL; let x: RBTreeNode, y: RBTreeNode; - while (node !== SN) { + while (node !== NIL) { if (node.key === key) { z = node; } @@ -97,16 +113,16 @@ export class RedBlackTree { } } - if (z === SN) { + if (z === NIL) { return; } y = z; let yOriginalColor: number = y.color; - if (z.left === SN) { + if (z.left === NIL) { x = z.right; this._rbTransplant(z, z.right); - } else if (z.right === SN) { + } else if (z.right === NIL) { x = z.left; this._rbTransplant(z, z.left); } else { @@ -133,8 +149,8 @@ export class RedBlackTree { helper(this.root); } - isRealNode(node: RBTreeNode): node is RBTreeNode { - return node !== SN && node !== null; + isRealNode(node: RBTreeNode | null | undefined): node is RBTreeNode { + return node !== NIL && node !== null; } /** @@ -170,7 +186,7 @@ export class RedBlackTree { * @returns The leftmost node in the given RBTreeNode. */ getLeftMost(node: RBTreeNode = this.root): RBTreeNode { - while (node.left !== null && node.left !== SN) { + while (node.left !== null && node.left !== NIL) { node = node.left; } return node; @@ -182,7 +198,7 @@ export class RedBlackTree { * @returns the rightmost node in a red-black tree. */ getRightMost(node: RBTreeNode): RBTreeNode { - while (node.right !== null && node.right !== SN) { + while (node.right !== null && node.right !== NIL) { node = node.right; } return node; @@ -194,12 +210,12 @@ export class RedBlackTree { * @returns the successor of the given RBTreeNode. */ getSuccessor(x: RBTreeNode): RBTreeNode { - if (x.right !== SN) { + if (x.right !== NIL) { return this.getLeftMost(x.right); } let y: RBTreeNode = x.parent; - while (y !== SN && y !== null && x === y.right) { + while (y !== NIL && y !== null && x === y.right) { x = y; y = y.parent; } @@ -213,12 +229,12 @@ export class RedBlackTree { * @returns the predecessor of the given RBTreeNode 'x'. */ getPredecessor(x: RBTreeNode): RBTreeNode { - if (x.left !== SN) { + if (x.left !== NIL) { return this.getRightMost(x.left); } let y: RBTreeNode = x.parent; - while (y !== SN && x === y.left) { + while (y !== NIL && x === y.left) { x = y; y = y.parent; } @@ -226,6 +242,65 @@ export class RedBlackTree { return y; } + print(beginRoot: RBTreeNode = this.root) { + const display = (root: RBTreeNode | null): void => { + const [lines, , ,] = _displayAux(root); + for (const line of lines) { + console.log(line); + } + }; + + const _displayAux = (node: RBTreeNode | null): [string[], number, number, number] => { + if (node === null) { + return [[], 0, 0, 0]; + } + + if (node.right === null && node.left === null) { + const line = `${node.key}`; + const width = line.length; + const height = 1; + const middle = Math.floor(width / 2); + return [[line], width, height, middle]; + } + + if (node.right === null) { + const [lines, n, p, x] = _displayAux(node.left); + const s = `${node.key}`; + const u = s.length; + const first_line = ' '.repeat(x + 1) + '_'.repeat(n - x - 1) + s; + const second_line = ' '.repeat(x) + '/' + ' '.repeat(n - x - 1 + u); + const shifted_lines = lines.map(line => line + ' '.repeat(u)); + return [[first_line, second_line, ...shifted_lines], n + u, p + 2, n + Math.floor(u / 2)]; + } + + if (node.left === null) { + const [lines, n, p, u] = _displayAux(node.right); + const s = `${node.key}`; + const x = s.length; + const first_line = s + '_'.repeat(x) + ' '.repeat(n - x); + const second_line = ' '.repeat(u + x) + '\\' + ' '.repeat(n - x - 1); + const shifted_lines = lines.map(line => ' '.repeat(u) + line); + return [[first_line, second_line, ...shifted_lines], n + x, p + 2, Math.floor(u / 2)]; + } + + const [left, n, p, x] = _displayAux(node.left); + const [right, m, q, y] = _displayAux(node.right); + const s = `${node.key}`; + const u = s.length; + const first_line = ' '.repeat(x + 1) + '_'.repeat(n - x - 1) + s + '_'.repeat(y) + ' '.repeat(m - y); + const second_line = ' '.repeat(x) + '/' + ' '.repeat(n - x - 1 + u + y) + '\\' + ' '.repeat(m - y - 1); + if (p < q) { + left.push(...new Array(q - p).fill(' '.repeat(n))); + } else if (q < p) { + right.push(...new Array(p - q).fill(' '.repeat(m))); + } + const zipped_lines = left.map((a, i) => a + ' '.repeat(u) + right[i]); + return [[first_line, second_line, ...zipped_lines], n + m + u, Math.max(p, q) + 2, n + Math.floor(u / 2)]; + }; + + display(beginRoot); + } + /** * The function performs a left rotation on a red-black tree node. * @param {RBTreeNode} x - The parameter `x` is a RBTreeNode object. @@ -233,7 +308,7 @@ export class RedBlackTree { protected _leftRotate(x: RBTreeNode): void { const y: RBTreeNode = x.right; x.right = y.left; - if (y.left !== SN) { + if (y.left !== NIL) { y.left.parent = x; } y.parent = x.parent; @@ -256,7 +331,7 @@ export class RedBlackTree { protected _rightRotate(x: RBTreeNode): void { const y: RBTreeNode = x.left; x.left = y.right; - if (y.right !== SN) { + if (y.right !== NIL) { y.right.parent = x; } y.parent = x.parent; diff --git a/test/unit/data-structures/binary-tree/rb-tree.test.ts b/test/unit/data-structures/binary-tree/rb-tree.test.ts index a929689..513b9fd 100644 --- a/test/unit/data-structures/binary-tree/rb-tree.test.ts +++ b/test/unit/data-structures/binary-tree/rb-tree.test.ts @@ -1,5 +1,8 @@ -import {RBTreeNode, RedBlackTree, SN} from '../../../../src'; +import {RBTNColor, RBTreeNode, RedBlackTree, NIL} from '../../../../src'; import {getRandomInt} from '../../../utils'; +import {isDebugTest} from '../../../config'; + +const isDebug = isDebugTest; describe('RedBlackTree', () => { let tree: RedBlackTree; @@ -63,7 +66,7 @@ describe('RedBlackTree', () => { test('should handle an empty tree', () => { const minNode = tree.getLeftMost(tree.root); - expect(minNode).toBe(SN); + expect(minNode).toBe(NIL); }); }); @@ -81,7 +84,7 @@ describe('RedBlackTree', () => { test('should handle an empty tree', () => { const maxNode = tree.getRightMost(tree.root); - expect(maxNode).toBe(SN); + expect(maxNode).toBe(NIL); }); }); @@ -105,7 +108,7 @@ describe('RedBlackTree', () => { const node = tree.getNode(10); const successorNode = tree.getSuccessor(node); - // TODO not sure if it should be null or SN + // TODO not sure if it should be null or NIL expect(successorNode).toBe(null); }); }); @@ -130,7 +133,7 @@ describe('RedBlackTree', () => { const node = tree.getNode(20); const predecessorNode = tree.getPredecessor(node); - // TODO not sure if it should be SN or something else. + // TODO not sure if it should be NIL or something else. expect(predecessorNode).toBe(tree.getNode(10)); }); }); @@ -198,14 +201,146 @@ describe('RedBlackTree', () => { expect(node.right.key).toBe(25); }); - it('should fix the tree after deletion', () => { + it('should all node attributes fully conform to the red-black tree standards.', () => { tree.insert(10); tree.insert(20); tree.insert(5); tree.insert(15); - tree.delete(15); - // Verify that the tree is still valid - // You can add assertions to check the Red-Black Tree properties + tree.insert(21); + tree.insert(6); + tree.insert(2); + + let node10F = tree.getNode(10); + let node20F = tree.getNode(20); + let node5F = tree.getNode(5); + let node15F = tree.getNode(15); + let node21F = tree.getNode(21); + let node6F = tree.getNode(6); + let node2F = tree.getNode(2); + expect(node10F.key).toBe(10); + expect(node10F.color).toBe(RBTNColor.BLACK); + expect(node10F.left).toBe(node5F); + expect(node10F.right).toBe(node20F); + expect(node10F.parent).toBe(null); + expect(node20F.key).toBe(20); + expect(node20F.color).toBe(RBTNColor.BLACK); + expect(node20F.left).toBe(node15F); + expect(node20F.right).toBe(node21F); + expect(node20F.parent).toBe(node10F); + expect(node5F.key).toBe(5); + expect(node5F.color).toBe(RBTNColor.BLACK); + expect(node5F.left).toBe(node2F); + expect(node5F.right).toBe(node6F); + expect(node5F.parent).toBe(node10F); + expect(node15F.key).toBe(15); + expect(node15F.color).toBe(RBTNColor.RED); + expect(node15F.left).toBe(NIL); + expect(node15F.right).toBe(NIL); + expect(node15F.parent).toBe(node20F); + expect(node21F.key).toBe(21); + expect(node21F.color).toBe(RBTNColor.RED); + expect(node21F.left).toBe(NIL); + expect(node21F.right).toBe(NIL); + expect(node21F.parent).toBe(node20F); + expect(node6F.key).toBe(6); + expect(node6F.color).toBe(RBTNColor.RED); + expect(node6F.left).toBe(NIL); + expect(node6F.right).toBe(NIL); + expect(node6F.parent).toBe(node5F); + expect(node2F.key).toBe(2); + expect(node2F.color).toBe(RBTNColor.RED); + expect(node2F.left).toBe(NIL); + expect(node2F.right).toBe(NIL); + expect(node2F.parent).toBe(node5F); + expect(node15F.key).toBe(15); + expect(node15F.color).toBe(RBTNColor.RED); + expect(node15F.left).toBe(NIL); + expect(node15F.right).toBe(NIL); + expect(node15F.parent).toBe(node20F); + tree.delete(5); + node10F = tree.getNode(10); + node20F = tree.getNode(20); + node5F = tree.getNode(5); + node15F = tree.getNode(15); + node21F = tree.getNode(21); + node6F = tree.getNode(6); + node2F = tree.getNode(2); + expect(node10F.key).toBe(10); + expect(node10F.color).toBe(RBTNColor.BLACK); + expect(node10F.left).toBe(node6F); + expect(node10F.right).toBe(node20F); + expect(node10F.parent).toBe(null); + expect(node20F.key).toBe(20); + expect(node20F.color).toBe(RBTNColor.BLACK); + expect(node20F.left).toBe(node15F); + expect(node20F.right).toBe(node21F); + expect(node20F.parent).toBe(node10F); + expect(node5F).toBe(null); + expect(node15F.key).toBe(15); + expect(node15F.color).toBe(RBTNColor.RED); + expect(node15F.left).toBe(NIL); + expect(node15F.right).toBe(NIL); + expect(node15F.parent).toBe(node20F); + expect(node21F.key).toBe(21); + expect(node21F.color).toBe(RBTNColor.RED); + expect(node21F.left).toBe(NIL); + expect(node21F.right).toBe(NIL); + expect(node21F.parent).toBe(node20F); + expect(node6F.key).toBe(6); + expect(node6F.color).toBe(RBTNColor.BLACK); + expect(node6F.left).toBe(node2F); + expect(node6F.right).toBe(NIL); + expect(node6F.parent).toBe(node10F); + expect(node2F.key).toBe(2); + expect(node2F.color).toBe(RBTNColor.RED); + expect(node2F.left).toBe(NIL); + expect(node2F.right).toBe(NIL); + expect(node2F.parent).toBe(node6F); + expect(node15F.key).toBe(15); + expect(node15F.color).toBe(RBTNColor.RED); + expect(node15F.left).toBe(NIL); + expect(node15F.right).toBe(NIL); + expect(node15F.parent).toBe(node20F); + tree.delete(20); + node10F = tree.getNode(10); + node20F = tree.getNode(20); + node5F = tree.getNode(5); + node15F = tree.getNode(15); + node21F = tree.getNode(21); + node6F = tree.getNode(6); + node2F = tree.getNode(2); + expect(node10F.key).toBe(10); + expect(node10F.color).toBe(RBTNColor.BLACK); + expect(node10F.left).toBe(node6F); + expect(node10F.right).toBe(node21F); + expect(node10F.parent).toBe(null); + expect(node20F).toBe(null); + expect(node5F).toBe(null); + expect(node15F.key).toBe(15); + expect(node15F.color).toBe(RBTNColor.RED); + expect(node15F.left).toBe(NIL); + expect(node15F.right).toBe(NIL); + expect(node15F.parent).toBe(node21F); + expect(node21F.key).toBe(21); + expect(node21F.color).toBe(RBTNColor.BLACK); + expect(node21F.left).toBe(node15F); + expect(node21F.right).toBe(NIL); + expect(node21F.parent).toBe(node10F); + expect(node6F.key).toBe(6); + expect(node6F.color).toBe(RBTNColor.BLACK); + expect(node6F.left).toBe(node2F); + expect(node6F.right).toBe(NIL); + expect(node6F.parent).toBe(node10F); + expect(node2F.key).toBe(2); + expect(node2F.color).toBe(RBTNColor.RED); + expect(node2F.left).toBe(NIL); + expect(node2F.right).toBe(NIL); + expect(node2F.parent).toBe(node6F); + expect(node15F.key).toBe(15); + expect(node15F.color).toBe(RBTNColor.RED); + expect(node15F.left).toBe(NIL); + expect(node15F.right).toBe(NIL); + expect(node15F.parent).toBe(node21F); }); it('should fix the tree after insertion', () => { @@ -214,8 +349,8 @@ describe('RedBlackTree', () => { tree.insert(5); tree.insert(15); const node15F = tree.getNode(15); - expect(node15F.left).toBe(SN); - expect(node15F.right).toBe(SN); + expect(node15F.left).toBe(NIL); + expect(node15F.right).toBe(NIL); expect(node15F.parent).toBe(tree.getNode(5)); tree.insert(25); @@ -230,8 +365,8 @@ describe('RedBlackTree', () => { tree.insert(155); tree.insert(225); const node225F = tree.getNode(225); - expect(node225F.left).toBe(SN); - expect(node225F.right).toBe(SN); + expect(node225F.left).toBe(NIL); + expect(node225F.right).toBe(NIL); expect(node225F.parent.key).toBe(155); tree.insert(7); @@ -257,16 +392,38 @@ describe('RedBlackTree', () => { const node50 = tree.getNode(50); expect(node50.key).toBe(50); expect(node50.left.key).toBe(33); - expect(node50.right).toBe(SN); + expect(node50.right).toBe(NIL); const node15Fo = tree.getNode(15); expect(node15Fo.key).toBe(15); - expect(node15Fo.left).toBe(SN); + expect(node15Fo.left).toBe(NIL); const node225S = tree.getNode(225); - expect(node225S.left).toBe(SN); - expect(node225S.right).toBe(SN); + expect(node225S.left).toBe(NIL); + expect(node225S.right).toBe(NIL); expect(node225S.parent.key).toBe(155); expect(tree.getNode(0)).toBe(null); + tree.insert(1); + tree.insert(2); + tree.insert(3); + tree.insert(4); + tree.insert(5); + tree.insert(6); + tree.insert(7); + tree.insert(8); + tree.insert(9); + tree.insert(10); + tree.insert(11); + tree.insert(12); + tree.insert(13); + tree.insert(14); + tree.insert(15); + tree.insert(16); + tree.insert(17); + tree.insert(18); + tree.insert(19); + tree.insert(110); + + isDebug && tree.print(); }); it('should fix the tree after insertion and deletion', () => {