From 97e6120b3f739d78960ef7b48f620e1b4bf8f128 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Wed, 4 Apr 2018 09:21:03 +0300 Subject: [PATCH] Refactor BST. --- .../binary-search-tree/BinarySearchTree.js | 36 +-------- .../BinarySearchTreeNode.js | 38 +++++++++ .../__test__/BinarySearchTree.test.js | 66 +--------------- .../__test__/BinarySearchTreeNode.test.js | 79 +++++++++++++++++++ 4 files changed, 119 insertions(+), 100 deletions(-) diff --git a/src/data-structures/tree/binary-search-tree/BinarySearchTree.js b/src/data-structures/tree/binary-search-tree/BinarySearchTree.js index d2e3682a..baa738a4 100644 --- a/src/data-structures/tree/binary-search-tree/BinarySearchTree.js +++ b/src/data-structures/tree/binary-search-tree/BinarySearchTree.js @@ -14,41 +14,7 @@ export default class BinarySearchTree { } remove(value) { - const nodeToRemove = this.root.find(value); - - if (!nodeToRemove) { - 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); - } - } + return this.root.remove(value); } toString() { diff --git a/src/data-structures/tree/binary-search-tree/BinarySearchTreeNode.js b/src/data-structures/tree/binary-search-tree/BinarySearchTreeNode.js index 144c250a..07e9eab2 100644 --- a/src/data-structures/tree/binary-search-tree/BinarySearchTreeNode.js +++ b/src/data-structures/tree/binary-search-tree/BinarySearchTreeNode.js @@ -47,6 +47,44 @@ export default class BinarySearchTreeNode extends BinaryTreeNode { return !!this.find(value); } + remove(value) { + const nodeToRemove = this.find(value); + + if (!nodeToRemove) { + 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); + } + } + } + findMin() { if (!this.left) { return this; 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 4ff35b44..bfc7b976 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 @@ -32,7 +32,7 @@ describe('BinarySearchTree', () => { expect(bst.contains(40)).toBeFalsy(); }); - it('should remove leaf nodes', () => { + it('should remove nodes', () => { const bst = new BinarySearchTree(); bst.insert(10); @@ -46,68 +46,4 @@ describe('BinarySearchTree', () => { 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(); - }); }); diff --git a/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTreeNode.test.js b/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTreeNode.test.js index 306a6fbf..cf386ef7 100644 --- a/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTreeNode.test.js +++ b/src/data-structures/tree/binary-search-tree/__test__/BinarySearchTreeNode.test.js @@ -92,4 +92,83 @@ describe('BinarySearchTreeNode', () => { expect(node.find(5)).not.toBeNull(); expect(node.find(5).value).toBe(5); }); + + it('should remove leaf nodes', () => { + const bstRootNode = new BinarySearchTreeNode(); + + bstRootNode.insert(10); + bstRootNode.insert(20); + bstRootNode.insert(5); + + expect(bstRootNode.toString()).toBe('5,10,20'); + + bstRootNode.remove(5); + expect(bstRootNode.toString()).toBe('10,20'); + bstRootNode.remove(20); + expect(bstRootNode.toString()).toBe('10'); + }); + + it('should remove nodes with one child', () => { + const bstRootNode = new BinarySearchTreeNode(); + + bstRootNode.insert(10); + bstRootNode.insert(20); + bstRootNode.insert(5); + bstRootNode.insert(30); + + expect(bstRootNode.toString()).toBe('5,10,20,30'); + + bstRootNode.remove(20); + expect(bstRootNode.toString()).toBe('5,10,30'); + + bstRootNode.insert(1); + expect(bstRootNode.toString()).toBe('1,5,10,30'); + + bstRootNode.remove(5); + expect(bstRootNode.toString()).toBe('1,10,30'); + }); + + it('should remove nodes with two children', () => { + const bstRootNode = new BinarySearchTreeNode(); + + bstRootNode.insert(10); + bstRootNode.insert(20); + bstRootNode.insert(5); + bstRootNode.insert(30); + bstRootNode.insert(15); + bstRootNode.insert(25); + + expect(bstRootNode.toString()).toBe('5,10,15,20,25,30'); + expect(bstRootNode.find(20).left.value).toBe(15); + expect(bstRootNode.find(20).right.value).toBe(30); + + bstRootNode.remove(20); + expect(bstRootNode.toString()).toBe('5,10,15,25,30'); + + bstRootNode.remove(15); + expect(bstRootNode.toString()).toBe('5,10,25,30'); + + bstRootNode.remove(10); + expect(bstRootNode.toString()).toBe('5,25,30'); + expect(bstRootNode.value).toBe(25); + + bstRootNode.remove(25); + expect(bstRootNode.toString()).toBe('5,30'); + + bstRootNode.remove(5); + expect(bstRootNode.toString()).toBe('30'); + }); + + it('should throw error when trying to remove not existing node', () => { + const bstRootNode = new BinarySearchTreeNode(); + + bstRootNode.insert(10); + bstRootNode.insert(20); + + function removeNotExistingElementFromTree() { + bstRootNode.remove(30); + } + + expect(removeNotExistingElementFromTree).toThrow(); + }); });