diff --git a/src/data-structures/tree/BinaryTreeNode.js b/src/data-structures/tree/BinaryTreeNode.js index 74e77866..9db31ea3 100644 --- a/src/data-structures/tree/BinaryTreeNode.js +++ b/src/data-structures/tree/BinaryTreeNode.js @@ -18,6 +18,38 @@ export default class BinaryTreeNode { return this; } + removeChild(nodeToRemove) { + if (this.left && this.left === nodeToRemove) { + this.left = null; + return true; + } + + if (this.right && this.right === nodeToRemove) { + this.right = null; + return true; + } + + return false; + } + + replaceChild(nodeToReplace, replacementNode) { + if (!nodeToReplace || !replacementNode) { + return false; + } + + if (this.left && this.left === nodeToReplace) { + this.left = replacementNode; + return true; + } + + if (this.right && this.right === nodeToReplace) { + this.right = replacementNode; + return true; + } + + return false; + } + traverseInOrder() { let traverse = []; diff --git a/src/data-structures/tree/__test__/BinaryTreeNode.test.js b/src/data-structures/tree/__test__/BinaryTreeNode.test.js index 7f68015e..36c61415 100644 --- a/src/data-structures/tree/__test__/BinaryTreeNode.test.js +++ b/src/data-structures/tree/__test__/BinaryTreeNode.test.js @@ -51,4 +51,53 @@ describe('BinaryTreeNode', () => { expect(rootNode.toString()).toBe('1,2,3'); }); + + it('should remove child node', () => { + const leftNode = new BinaryTreeNode(1); + const rightNode = new BinaryTreeNode(3); + const rootNode = new BinaryTreeNode(2); + + rootNode + .setLeft(leftNode) + .setRight(rightNode); + + expect(rootNode.traverseInOrder()).toEqual([1, 2, 3]); + + expect(rootNode.removeChild(rootNode.left)).toBeTruthy(); + expect(rootNode.traverseInOrder()).toEqual([2, 3]); + + expect(rootNode.removeChild(rootNode.right)).toBeTruthy(); + expect(rootNode.traverseInOrder()).toEqual([2]); + + expect(rootNode.removeChild(rootNode.right)).toBeFalsy(); + expect(rootNode.traverseInOrder()).toEqual([2]); + }); + + it('should replace child node', () => { + const leftNode = new BinaryTreeNode(1); + const rightNode = new BinaryTreeNode(3); + const rootNode = new BinaryTreeNode(2); + + rootNode + .setLeft(leftNode) + .setRight(rightNode); + + expect(rootNode.traverseInOrder()).toEqual([1, 2, 3]); + + const replacementNode = new BinaryTreeNode(5); + rightNode.setRight(replacementNode); + + expect(rootNode.traverseInOrder()).toEqual([1, 2, 3, 5]); + + expect(rootNode.replaceChild(rootNode.right, rootNode.right.right)).toBeTruthy(); + expect(rootNode.right.value).toBe(5); + expect(rootNode.right.right).toBeNull(); + expect(rootNode.traverseInOrder()).toEqual([1, 2, 5]); + + expect(rootNode.replaceChild(rootNode.right, rootNode.right.right)).toBeFalsy(); + expect(rootNode.traverseInOrder()).toEqual([1, 2, 5]); + + expect(rootNode.replaceChild(rootNode.right, replacementNode)).toBeTruthy(); + expect(rootNode.traverseInOrder()).toEqual([1, 2, 5]); + }); }); diff --git a/src/data-structures/tree/binary-search-tree/BinarySearchTree.js b/src/data-structures/tree/binary-search-tree/BinarySearchTree.js index 904650ee..d2e3682a 100644 --- a/src/data-structures/tree/binary-search-tree/BinarySearchTree.js +++ b/src/data-structures/tree/binary-search-tree/BinarySearchTree.js @@ -20,16 +20,34 @@ export default class BinarySearchTree { throw new Error('Item not found in the tree'); } + const { parent } = nodeToRemove; + if (!nodeToRemove.left && !nodeToRemove.right) { // Node is a leaf and thus has no children. // Just remove the pointer to this node from the parent node. + parent.removeChild(nodeToRemove); } else if (nodeToRemove.left && nodeToRemove.right) { // Node has two children. // Find the next biggest value (minimum value in the right branch) // and replace current value node with that next biggest value. + const nextBiggerNode = nodeToRemove.right.findMin(); + if (nextBiggerNode !== nodeToRemove.right) { + this.remove(nextBiggerNode.value); + nodeToRemove.value = nextBiggerNode.value; + } else { + // In case if next right value is the next bigger one and it doesn't have left child + // then just replace node that is going to be deleted with the right node. + nodeToRemove.value = nodeToRemove.right.value; + nodeToRemove.right = nodeToRemove.right.right; + } } else { // Node has only one child. // Make this child to be a direct child of current node's parent. + if (nodeToRemove.left) { + parent.replaceChild(nodeToRemove, nodeToRemove.left); + } else { + parent.replaceChild(nodeToRemove, nodeToRemove.right); + } } } diff --git a/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTree.test.js b/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTree.test.js index 55a0c474..4ff35b44 100644 --- a/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTree.test.js +++ b/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTree.test.js @@ -31,4 +31,83 @@ describe('BinarySearchTree', () => { expect(bst.contains(20)).toBeTruthy(); expect(bst.contains(40)).toBeFalsy(); }); + + it('should remove leaf nodes', () => { + const bst = new BinarySearchTree(); + + bst.insert(10); + bst.insert(20); + bst.insert(5); + + expect(bst.toString()).toBe('5,10,20'); + + bst.remove(5); + expect(bst.toString()).toBe('10,20'); + bst.remove(20); + expect(bst.toString()).toBe('10'); + }); + + it('should remove nodes with one child', () => { + const bst = new BinarySearchTree(); + + bst.insert(10); + bst.insert(20); + bst.insert(5); + bst.insert(30); + + expect(bst.toString()).toBe('5,10,20,30'); + + bst.remove(20); + expect(bst.toString()).toBe('5,10,30'); + + bst.insert(1); + expect(bst.toString()).toBe('1,5,10,30'); + + bst.remove(5); + expect(bst.toString()).toBe('1,10,30'); + }); + + it('should remove nodes with two children', () => { + const bst = new BinarySearchTree(); + + bst.insert(10); + bst.insert(20); + bst.insert(5); + bst.insert(30); + bst.insert(15); + bst.insert(25); + + expect(bst.toString()).toBe('5,10,15,20,25,30'); + expect(bst.root.find(20).left.value).toBe(15); + expect(bst.root.find(20).right.value).toBe(30); + + bst.remove(20); + expect(bst.toString()).toBe('5,10,15,25,30'); + + bst.remove(15); + expect(bst.toString()).toBe('5,10,25,30'); + + bst.remove(10); + expect(bst.toString()).toBe('5,25,30'); + expect(bst.root.value).toBe(25); + + bst.remove(25); + expect(bst.toString()).toBe('5,30'); + + bst.remove(5); + expect(bst.toString()).toBe('30'); + }); + + it('should throw error when trying to remove not existing node', () => { + const bst = new BinarySearchTree(); + + bst.insert(10); + bst.insert(20); + + function removeNotExistingElementFromTree() { + bst.remove(30); + } + + expect(removeNotExistingElementFromTree).toThrow(); + }); });