[binary-tree] Let the get method call getNode instead of getNodes. [rbtree] Use rigorous testing to verify that the properties of nodes in a red-black tree adhere to the red-black tree rules after insertion and deletion.

This commit is contained in:
Revone 2023-11-01 17:51:38 +08:00
parent c82190764e
commit e9f2359365
5 changed files with 285 additions and 53 deletions

View file

@ -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

4
package-lock.json generated
View file

@ -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",

View file

@ -559,21 +559,21 @@ export class BinaryTree<V = any, N extends BinaryTreeNode<V, N> = BinaryTreeNode
has<C extends BTNCallback<N, BTNKey>>(
identifier: BTNKey,
callback?: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): boolean;
has<C extends BTNCallback<N, N>>(
identifier: N | null,
callback?: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): boolean;
has<C extends BTNCallback<N>>(
identifier: ReturnType<C> | null,
callback: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): boolean;
@ -601,28 +601,28 @@ export class BinaryTree<V = any, N extends BinaryTreeNode<V, N> = 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<C extends BTNCallback<N, BTNKey>>(
identifier: BTNKey,
callback?: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): N | null;
getNode<C extends BTNCallback<N, N>>(
identifier: N | null,
callback?: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): N | null;
getNode<C extends BTNCallback<N>>(
identifier: ReturnType<C>,
callback: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): N | null;
@ -648,28 +648,28 @@ export class BinaryTree<V = any, N extends BinaryTreeNode<V, N> = 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<C extends BTNCallback<N, BTNKey>>(
identifier: BTNKey,
callback?: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): V | undefined;
get<C extends BTNCallback<N, N>>(
identifier: N | null,
callback?: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): V | undefined;
get<C extends BTNCallback<N>>(
identifier: ReturnType<C>,
callback: C,
beginRoot?: N,
beginRoot?: N | null,
iterationType?: IterationType
): V | undefined;
@ -695,8 +695,8 @@ export class BinaryTree<V = any, N extends BinaryTreeNode<V, N> = 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;
}
/**

View file

@ -1,4 +1,13 @@
/**
* data-structure-typed
*
* @author Tyler Zeng
* @copyright Copyright (c) 2022 Tyler Zeng <zrwusa@gmail.com>
* @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;

View file

@ -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', () => {