diff --git a/examples/chapter10/01-UsingMinHeap.js b/examples/chapter10/01-UsingMinHeap.js index 94d98e7c..59b7753b 100644 --- a/examples/chapter10/01-UsingMinHeap.js +++ b/examples/chapter10/01-UsingMinHeap.js @@ -22,6 +22,6 @@ for (let i = 1; i < 10; i++) { console.log(heap.getAsArray()); -console.log('Extract minimum: ', heap.extract()); +console.log('Extract minimum: ', heap.extract()); // 1 console.log(heap.getAsArray()); // [2, 4, 3, 8, 5, 6, 7, 9] diff --git a/src/ts/data-structures/models/red-black-node.ts b/src/ts/data-structures/models/red-black-node.ts index 256bd7f5..20da6f60 100644 --- a/src/ts/data-structures/models/red-black-node.ts +++ b/src/ts/data-structures/models/red-black-node.ts @@ -1,14 +1,20 @@ +import { Node } from './node'; + export enum Colors { RED = 0, BLACK = 1 } -export class RedBlackNode { +export class RedBlackNode extends Node { left: RedBlackNode; right: RedBlackNode; + parent: RedBlackNode; color: Colors; - constructor(public key: K) {} + constructor(public key: K) { + super(key); + this.color = Colors.RED; + } isRed() { return this.color === Colors.RED; @@ -21,8 +27,4 @@ export class RedBlackNode { this.color = Colors.RED; } } - - toString() { - return `${this.key}`; - } } diff --git a/src/ts/data-structures/red-black-tree.ts b/src/ts/data-structures/red-black-tree.ts index 001a2fb4..b58b71df 100644 --- a/src/ts/data-structures/red-black-tree.ts +++ b/src/ts/data-structures/red-black-tree.ts @@ -1,11 +1,180 @@ -/*import { Compare, defaultCompare, ICompareFunction } from '../util'; +import { defaultCompare, ICompareFunction, Compare } from '../util'; import BinarySearchTree from './binary-search-tree'; -import { Node } from './models/node'; +import { RedBlackNode, Colors } from './models/red-black-node'; export default class RedBlackTree extends BinarySearchTree { + protected root: RedBlackNode; constructor(protected compareFn: ICompareFunction = defaultCompare) { super(compareFn); } + + /** + * Left left case: rotate right + * + * b a + * / \ / \ + * a e -> rotationLL(b) -> c b + * / \ / \ + * c d d e + * + * @param node Node + */ + private rotationLL(node: RedBlackNode) { + const tmp = node.left; + node.left = tmp.right; + if (tmp.right && tmp.right.key) { + tmp.right.parent = node; + } + tmp.parent = node.parent; + if (!node.parent) { + this.root = tmp; + } else { + if (node === node.parent.left) { + node.parent.left = tmp; + } else { + node.parent.right = tmp; + } + } + tmp.right = node; + node.parent = tmp; + } + + /** + * Right right case: rotate left + * + * a b + * / \ / \ + * c b -> rotationRR(a) -> a e + * / \ / \ + * d e c d + * + * @param node Node + */ + private rotationRR(node: RedBlackNode) { + const tmp = node.right; + node.right = tmp.left; + if (tmp.left && tmp.left.key) { + tmp.left.parent = node; + } + tmp.parent = node.parent; + if (!node.parent) { + this.root = tmp; + } else { + if (node === node.parent.left) { + node.parent.left = tmp; + } else { + node.parent.right = tmp; + } + } + tmp.left = node; + node.parent = tmp; + } + + insert(key: T) { + // special case: first key + if (this.root == null) { + this.root = new RedBlackNode(key); + this.root.color = Colors.BLACK; + } else { + const newNode = this.insertNode(this.root, key); + this.fixTreeProperties(newNode); + } + } + + protected insertNode(node: RedBlackNode, key: T): RedBlackNode { + if (this.compareFn(key, node.key) === Compare.LESS_THAN) { + if (node.left == null) { + node.left = new RedBlackNode(key); + node.left.parent = node; + return node.left; + } else { + return this.insertNode(node.left, key); + } + } else if (node.right == null) { + node.right = new RedBlackNode(key); + node.right.parent = node; + return node.right; + } else { + return this.insertNode(node.right, key); + } + } + + private fixTreeProperties(node: RedBlackNode) { + while (node && node.parent && node.parent.color === Colors.RED && node.color !== Colors.BLACK) { + let parent = node.parent; + const grandParent = parent.parent; + + // case A + if (grandParent && grandParent.left === parent) { + + const uncle = grandParent.right; + + // case 1: uncle of node is also red - only recoloring + if (uncle && uncle.color === Colors.RED) { + grandParent.color = Colors.RED; + parent.color = Colors.BLACK; + uncle.color = Colors.BLACK; + node = grandParent; + } else { + // case 2: node is right child - left rotate + if (node === parent.right) { + this.rotationRR(parent); + node = parent; + parent = node.parent; + } + + // case 3: node is left child - right rotate + this.rotationLL(grandParent); + // swap color + parent.color = Colors.BLACK; + grandParent.color = Colors.RED; + node = parent; + } + + } else { // case B: parent is right child of grand parent + + const uncle = grandParent.left; + + // case 1: uncle is read - only recoloring + if (uncle && uncle.color === Colors.RED) { + grandParent.color = Colors.RED; + parent.color = Colors.BLACK; + uncle.color = Colors.BLACK; + node = grandParent; + } else { + // case 2: node is left child - left rotate + if (node === parent.left) { + this.rotationLL(parent); + node = parent; + parent = node.parent; + } + + // case 3: node is right child - left rotate + this.rotationRR(grandParent); + // swap color + parent.color = Colors.BLACK; + grandParent.color = Colors.RED; + node = parent; + } + } + } + this.root.color = Colors.BLACK; + } + + getRoot() { + return this.root; + } + + /* private flipColors(node: RedBlackNode) { + node.left.flipColor(); + node.right.flipColor(); + } + + private isRed(node: RedBlackNode) { + if (!node) { + return false; + } + return node.isRed(); + }*/ } -*/ diff --git a/src/ts/index.ts b/src/ts/index.ts index 661de1f2..b1e507e1 100644 --- a/src/ts/index.ts +++ b/src/ts/index.ts @@ -23,6 +23,7 @@ export { fibonacciMemoization as fibonacciMemoization} from './others/fibonacci' // chapter 09 export { default as BinarySearchTree } from './data-structures/binary-search-tree'; export { default as AVLTree } from './data-structures/avl-tree'; +export { default as RedBlackTree } from './data-structures/red-black-tree'; // chapter 10 export { MinHeap as MinHeap } from './data-structures/heap'; diff --git a/test/ts/data-structures/red-black-tree.spec.ts b/test/ts/data-structures/red-black-tree.spec.ts new file mode 100644 index 00000000..8fefc0f5 --- /dev/null +++ b/test/ts/data-structures/red-black-tree.spec.ts @@ -0,0 +1,112 @@ +import { Colors } from './../../../src/ts/data-structures/models/red-black-node'; +import 'mocha'; +import { expect } from 'chai'; +import { RedBlackTree } from '../../../src/ts/index'; + +describe('RedBlackTree', () => { + let tree: RedBlackTree; + + beforeEach(() => { + tree = new RedBlackTree(); + }); + + it('starts empty', () => { + expect(tree.getRoot()).to.equal(undefined); + }); + + it('inserts elements in the RedBlackTree', () => { + expect(tree.getRoot()).to.equal(undefined); + + let node; + + tree.insert(1); + assertNode(tree.getRoot(), 1, Colors.BLACK); + + tree.insert(2); + assertNode(tree.getRoot(), 1, Colors.BLACK); + assertNode(tree.getRoot().right, 2, Colors.RED); + + tree.insert(3); + assertNode(tree.getRoot(), 2, Colors.BLACK); + assertNode(tree.getRoot().right, 3, Colors.RED); + assertNode(tree.getRoot().left, 1, Colors.RED); + + tree.insert(4); + assertNode(tree.getRoot(), 2, Colors.BLACK); + assertNode(tree.getRoot().left, 1, Colors.BLACK); + assertNode(tree.getRoot().right, 3, Colors.BLACK); + assertNode(tree.getRoot().right.right, 4, Colors.RED); + + tree.insert(5); + assertNode(tree.getRoot(), 2, Colors.BLACK); + assertNode(tree.getRoot().left, 1, Colors.BLACK); + node = tree.getRoot().right; + assertNode(node, 4, Colors.BLACK); + assertNode(node.left, 3, Colors.RED); + assertNode(node.right, 5, Colors.RED); + + tree.insert(6); + assertNode(tree.getRoot(), 2, Colors.BLACK); + assertNode(tree.getRoot().left, 1, Colors.BLACK); + node = tree.getRoot().right; + assertNode(node, 4, Colors.RED); + assertNode(node.left, 3, Colors.BLACK); + assertNode(node.right, 5, Colors.BLACK); + assertNode(node.right.right, 6, Colors.RED); + + tree.insert(7); + assertNode(tree.getRoot(), 2, Colors.BLACK); + assertNode(tree.getRoot().left, 1, Colors.BLACK); + node = tree.getRoot().right; + assertNode(node, 4, Colors.RED); + assertNode(node.left, 3, Colors.BLACK); + assertNode(node.right, 6, Colors.BLACK); + assertNode(node.right.right, 7, Colors.RED); + assertNode(node.right.left, 5, Colors.RED); + + tree.insert(8); + assertNode(tree.getRoot(), 4, Colors.BLACK); + node = tree.getRoot().left; + assertNode(node, 2, Colors.RED); + assertNode(node.left, 1, Colors.BLACK); + assertNode(node.right, 3, Colors.BLACK); + node = tree.getRoot().right; + assertNode(node, 6, Colors.RED); + assertNode(node.left, 5, Colors.BLACK); + assertNode(node.right, 7, Colors.BLACK); + assertNode(node.right.right, 8, Colors.RED); + + tree.insert(9); + assertNode(tree.getRoot(), 4, Colors.BLACK); + node = tree.getRoot().left; + assertNode(node, 2, Colors.RED); + assertNode(node.left, 1, Colors.BLACK); + assertNode(node.right, 3, Colors.BLACK); + node = tree.getRoot().right; + assertNode(node, 6, Colors.RED); + assertNode(node.left, 5, Colors.BLACK); + assertNode(node.right, 8, Colors.BLACK); + assertNode(node.right.left, 7, Colors.RED); + assertNode(node.right.right, 9, Colors.RED); + + tree.insert(10); + assertNode(tree.getRoot(), 4, Colors.BLACK); + node = tree.getRoot().left; + assertNode(node, 2, Colors.BLACK); + assertNode(node.left, 1, Colors.BLACK); + assertNode(node.right, 3, Colors.BLACK); + node = tree.getRoot().right; + assertNode(node, 6, Colors.BLACK); + assertNode(node.left, 5, Colors.BLACK); + assertNode(node.right, 8, Colors.RED); + assertNode(node.right.left, 7, Colors.BLACK); + assertNode(node.right.right, 9, Colors.BLACK); + assertNode(node.right.right.right, 10, Colors.RED); + + }); + + function assertNode(node, key, color) { + expect(node.color).to.equal(color); + expect(node.key).to.equal(key); + } +});