-
Notifications
You must be signed in to change notification settings - Fork 314
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: AVL Implementation of BinarySearchTree (#67)
* refactor: exposed insertImpl and deleteImpl as protected methods * chore: setup launch.jsonuseful for vscode Jest debugging. Just place breakpoints on your code and Ctrl+F5 away to debug :)Hints: 'test.skip' and 'test.only' are useful to run only selected tests * feat: add AvtTree the AvtTree is inherited from BinarySearchTree. However duplicates are not properly handled yet in Avl. * Update index.ts Co-authored-by: Yangshun Tay <[email protected]>
- Loading branch information
Showing
6 changed files
with
294 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"configurations": [ | ||
{ | ||
"type": "node", | ||
"request": "launch", | ||
"name": "Jest Tests", | ||
"program": "${workspaceRoot}/node_modules/tsdx/dist/index.js", | ||
"args": ["test", "--runInBand"], | ||
"internalConsoleOptions": "openOnSessionStart" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import BinaryTreeNode from './BinaryTreeNode'; | ||
import BinarySearchTree from './BinarySearchTree'; | ||
|
||
/** | ||
* An implementation of AvlTree based on BinarySearchTrees | ||
*/ | ||
class AvlTree extends BinarySearchTree { | ||
protected _insertImpl( | ||
value: number, | ||
node: BinaryTreeNode<number> | null, | ||
): BinaryTreeNode<number> { | ||
return this._balance(super._insertImpl(value, node)); | ||
} | ||
|
||
protected _deleteImpl = ( | ||
value: number, | ||
node: BinaryTreeNode<number> | null, | ||
): BinaryTreeNode<number> | null => { | ||
node = super._deleteImpl(value, node); | ||
return node ? this._balance(node!) : node; | ||
}; | ||
|
||
_balance(node: BinaryTreeNode<number>): BinaryTreeNode<number> { | ||
// Helper function to calculate the balance of the node | ||
const calcBalance = (node: BinaryTreeNode<number>): number => | ||
(node.left ? node.left!.height() + 1 : 0) - | ||
(node.right ? node.right?.height() + 1 : 0); | ||
|
||
// Check for whether the node is out-of-balance | ||
const balance = calcBalance(node); | ||
|
||
if (balance > 1) { | ||
const leftBalance = calcBalance(node.left!); | ||
if (leftBalance > 0) { | ||
// LL case | ||
node = this._rotateRight(node); | ||
} else if (leftBalance < 0) { | ||
// LR case | ||
node.left = this._rotateLeft(node.left!); | ||
node = this._rotateRight(node); | ||
} | ||
} else if (balance < -1) { | ||
const rightBalance = calcBalance(node.right!); | ||
if (rightBalance > 0) { | ||
// RL case | ||
node.right = this._rotateRight(node.right!); | ||
node = this._rotateLeft(node); | ||
} else if (rightBalance < 0) { | ||
// RR case | ||
node = this._rotateLeft(node); | ||
} | ||
} | ||
|
||
return node; | ||
} | ||
|
||
_rotateRight(node: BinaryTreeNode<number>): BinaryTreeNode<number> { | ||
const { left } = node; | ||
const { right } = left!; | ||
|
||
left!.right = node; | ||
node.left = right; | ||
|
||
return left!; | ||
} | ||
|
||
_rotateLeft(node: BinaryTreeNode<number>): BinaryTreeNode<number> { | ||
const { right } = node; | ||
const { left } = right!; | ||
|
||
right!.left = node; | ||
node.right = left; | ||
|
||
return right!; | ||
} | ||
} | ||
|
||
export default AvlTree; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import _ from 'lodash'; | ||
import { AvlTree, BinaryTreeNode } from '../../src'; | ||
|
||
import { binarySearchTreeTests } from './BinarySearchTree.test'; | ||
|
||
describe('AvtTree', () => { | ||
describe('normal binary test', () => { | ||
// Test against normal BST test cases | ||
binarySearchTreeTests(describe, test); | ||
}); | ||
|
||
describe('self-balancing', () => { | ||
describe('insert', () => { | ||
test('LL rotation', () => { | ||
const tree = new AvlTree(); | ||
tree.insert(10); | ||
tree.insert(9); | ||
tree.insert(8); | ||
expect(tree.height()).toBe(1); | ||
|
||
tree.insert(11); | ||
expect(tree.height()).toBe(2); | ||
}); | ||
|
||
test('LR rotation', () => { | ||
const tree = new AvlTree(); | ||
tree.insert(10); | ||
tree.insert(8); | ||
tree.insert(9); | ||
expect(tree.height()).toBe(1); | ||
|
||
tree.insert(11); | ||
expect(tree.height()).toBe(2); | ||
}); | ||
|
||
test('RR rotation', () => { | ||
const tree = new AvlTree(); | ||
tree.insert(8); | ||
tree.insert(9); | ||
tree.insert(10); | ||
expect(tree.height()).toBe(1); | ||
|
||
tree.insert(11); | ||
expect(tree.height()).toBe(2); | ||
}); | ||
|
||
test('RL rotation', () => { | ||
const tree = new AvlTree(); | ||
tree.insert(8); | ||
tree.insert(10); | ||
tree.insert(9); | ||
expect(tree.height()).toBe(1); | ||
|
||
tree.insert(11); | ||
expect(tree.height()).toBe(2); | ||
}); | ||
}); | ||
|
||
describe('delete', () => { | ||
test('LL rotation', () => { | ||
const tree = new AvlTree(); | ||
tree.insert(5); | ||
tree.insert(3); | ||
tree.insert(8); | ||
tree.insert(4); | ||
tree.insert(2); | ||
tree.insert(10); | ||
tree.insert(1); | ||
expect(tree.height()).toBe(3); | ||
|
||
tree.delete(10); | ||
expect(tree.height()).toBe(2); | ||
}); | ||
|
||
test('LR rotation', () => { | ||
const tree = new AvlTree(); | ||
tree.insert(100); | ||
tree.insert(200); | ||
tree.insert(10); | ||
tree.insert(1); | ||
tree.insert(50); | ||
tree.insert(1000); | ||
tree.insert(40); | ||
tree.insert(60); | ||
expect(tree.height()).toBe(3); | ||
|
||
tree.delete(1000); | ||
expect(tree.height()).toBe(2); | ||
}); | ||
}); | ||
|
||
describe('duplicate', () => { | ||
test.skip('test for duplicates in AVL', () => { | ||
expect(true).toBe(true); | ||
}); | ||
}); | ||
|
||
test.skip('random stress test', () => { | ||
const assertAvl = (node: BinaryTreeNode<number> | null) => { | ||
if (!node) return; | ||
|
||
// assert BST property | ||
if (node.left) | ||
expect(node.value).toBeGreaterThanOrEqual(node.left.value); | ||
if (node.right) expect(node.value).toBeLessThan(node.right.value); | ||
|
||
// assert balancing property | ||
const balance = | ||
(node.left ? node.left!.height() + 1 : 0) - | ||
(node.right ? node.right?.height() + 1 : 0); | ||
expect(Math.abs(balance)).toBeLessThanOrEqual(1); | ||
|
||
assertAvl(node.left); | ||
assertAvl(node.right); | ||
}; | ||
|
||
const tree = new AvlTree(); | ||
|
||
// Build a tree of 100 random numbers, assert the tree is AVL tree | ||
_.times(100, () => { | ||
tree.insert(Math.floor(Math.random() * 50)); | ||
|
||
assertAvl(tree.root); | ||
}); | ||
|
||
// Delete off 50 random numbers, assert the tree is AVL the whole time | ||
const arr = tree.inOrder(); | ||
_.times(50, () => { | ||
const index = Math.floor(Math.random() * arr.length); | ||
tree.delete(arr[index]); | ||
arr.splice(index, 1); | ||
|
||
assertAvl(tree.root); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.