mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-26 23:21:18 +08:00
Refactor BST.
This commit is contained in:
parent
857edbf3a8
commit
97e6120b3f
@ -14,41 +14,7 @@ export default class BinarySearchTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove(value) {
|
remove(value) {
|
||||||
const nodeToRemove = this.root.find(value);
|
return this.root.remove(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
|
@ -47,6 +47,44 @@ export default class BinarySearchTreeNode extends BinaryTreeNode {
|
|||||||
return !!this.find(value);
|
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() {
|
findMin() {
|
||||||
if (!this.left) {
|
if (!this.left) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -32,7 +32,7 @@ describe('BinarySearchTree', () => {
|
|||||||
expect(bst.contains(40)).toBeFalsy();
|
expect(bst.contains(40)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove leaf nodes', () => {
|
it('should remove nodes', () => {
|
||||||
const bst = new BinarySearchTree();
|
const bst = new BinarySearchTree();
|
||||||
|
|
||||||
bst.insert(10);
|
bst.insert(10);
|
||||||
@ -46,68 +46,4 @@ describe('BinarySearchTree', () => {
|
|||||||
bst.remove(20);
|
bst.remove(20);
|
||||||
expect(bst.toString()).toBe('10');
|
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();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -92,4 +92,83 @@ describe('BinarySearchTreeNode', () => {
|
|||||||
expect(node.find(5)).not.toBeNull();
|
expect(node.find(5)).not.toBeNull();
|
||||||
expect(node.find(5).value).toBe(5);
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user