diff --git a/README.md b/README.md index 173c0ad..3af1689 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Trigger production build Data structures covered so far - - [Binary Search Tree](#binary-search-tree) +- [AVL Tree](#avl-tree) - In Progress - [Graph](#graph) - [Queue](#queue) - [Linked List](#link-list) @@ -130,6 +131,8 @@ Delete elements from binary search tree bst.delete(10); bst.delete(20); ``` +# AVL Tree +AVL Tree Docs. # Graph diff --git a/package.json b/package.json index 437d2f0..2eb053d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "prepare": "npm run build & npm run dev", "publish:npm": "npm publish --access public", "test:jest": "jest", + "test:jest:watch": "jest --watch", "lint": "eslint src/ --ext .ts" }, "jest": { @@ -44,7 +45,8 @@ "es6", "queue", "graph", - "binary search tree" + "binary search tree", + "avl tree" ], "author": { "name": "Abhishek Prakash", diff --git a/src/avl-tree/__test__/avl-node.spec.ts b/src/avl-tree/__test__/avl-node.spec.ts new file mode 100644 index 0000000..e1bb12d --- /dev/null +++ b/src/avl-tree/__test__/avl-node.spec.ts @@ -0,0 +1,59 @@ +import { AVLNode } from "../avl-node"; + +class Int extends AVLNode { + constructor(value: number) { + super(value) + } + + public compareTo(o: AVLNode): number { + if (this.getValue() < o.getValue()) { + return -1; + } else if (this.getValue() > o.getValue()) { + return 1; + } else { + return 0; + } + } +} + +describe("Test for AVLNode", () => { + let node: AVLNode; + let node1: AVLNode; + let node2: AVLNode; + let node3: AVLNode; + + beforeEach(() => { + node = new Int(5); + node1 = new Int(4); + node2 = new Int(5); + node3 = new Int(6); + }); + + it("should create an AVL node of type number", () => { + expect(node).toBeDefined(); + expect(node.getValue()).toBe(5); + expect(node.getLeft()).toBe(null); + expect(node.getRight()).toBe(null); + expect(node.getLeftHeight()).toBe(0); + expect(node.getRightHeight()).toBe(0); + expect(node.nodeHeight()).toBe(0); + expect(node.balanceFactor()).toBe(0); + expect(node.compareTo(node1)).toBe(1); + expect(node.compareTo(node2)).toBe(0); + expect(node.compareTo(node3)).toBe(-1); + }); + + it("should calculate the hight correctly", () => { + node.setLeft(node1); + node.setRight(node3); + node.setLeftHeight(1); + node.setRightHeight(1) + + expect(node.getLeft()).toEqual(node1); + expect(node.getRight()).toEqual(node3); + expect(node.getLeftHeight()).toBe(1); + expect(node.getRightHeight()).toBe(1); + expect(node.nodeHeight()).toBe(1); + expect(node.balanceFactor()).toBe(0); + }) +}); \ No newline at end of file diff --git a/src/avl-tree/__test__/avl.spec.ts b/src/avl-tree/__test__/avl.spec.ts new file mode 100644 index 0000000..67e0da3 --- /dev/null +++ b/src/avl-tree/__test__/avl.spec.ts @@ -0,0 +1,58 @@ +import { AVLTree } from "../avl" +import { AVLNode } from "../avl-node"; + +class Int extends AVLNode { + constructor(value: number) { + super(value) + } + + public compareTo(o: AVLNode): number { + if (this.getValue() < o.getValue()) { + return -1; + } else if (this.getValue() > o.getValue()) { + return 1; + } else { + return 0; + } + } +} + +describe("AVL Tree", () => { + describe("Insert operation", () => { + const avlTree = new AVLTree(); + + it("should have null node", () => { + const root = avlTree.getRoot(); + expect(root).toBe(null); + }); + + it("should add a node as root node in an empty tree", () => { + const isNodeInsterted = avlTree.insert(new Int(5)); + const root = avlTree.getRoot(); + expect(isNodeInsterted).toBeTruthy(); + expect(root.getValue()).toBe(5); + expect(root.nodeHeight()).toBe(0); + }); + + it("should add a left child to the avl tree", () => { + const isNodeInsterted = avlTree.insert(new Int(3)); + const left = avlTree.getRoot().getLeft(); + expect(isNodeInsterted).toBeTruthy(); + expect(left.getValue()).toBe(3); + expect(avlTree.getRoot().nodeHeight()).toBe(1); + }); + + it("should add a right child to the avl tree", () => { + const isNodeInsterted = avlTree.insert(new Int(9)); + const right = avlTree.getRoot().getRight(); + expect(isNodeInsterted).toBeTruthy(); + expect(right.getValue()).toBe(9); + expect(avlTree.getRoot().nodeHeight()).toBe(1); + }); + + it("should not add node with the same value", () => { + const isNodeInsterted = avlTree.insert(new Int(9)); + expect(isNodeInsterted).toBeFalsy(); + }) + }) +}) \ No newline at end of file diff --git a/src/avl-tree/avl-node.ts b/src/avl-tree/avl-node.ts new file mode 100644 index 0000000..41c4e36 --- /dev/null +++ b/src/avl-tree/avl-node.ts @@ -0,0 +1,89 @@ +export abstract class AVLNode { + private left: AVLNode; + private right: AVLNode; + private leftHeight: number; + private rightHeight: number; + private value: number; + private parent: AVLNode + + + constructor(value: number) { + this.left = null; + this.right = null; + this.parent = null; + this.leftHeight = 0; + this.rightHeight = 0; + this.value = value; + } + + public balanceFactor(): number { + return (this.rightHeight - this.leftHeight); + } + + public nodeHeight(): number { + return (this.rightHeight > this.leftHeight) ? this.rightHeight : this.leftHeight; + } + + public setNodeHeights(): void { + if (this.right == null) { + this.rightHeight = 0; + } else { + this.rightHeight = this.right.nodeHeight() + 1; + } + + if (this.left == null) { + this.leftHeight = 0; + } else { + this.leftHeight = this.left.nodeHeight() + 1; + } + } + + public abstract compareTo(o: AVLNode): number + + /* #region(collapsed) Getter and Setter */ + public getLeft(): AVLNode { + return this.left; + } + + public setLeft(left: AVLNode): void { + this.left = left; + } + + public getRight(): AVLNode { + return this.right; + } + + public setRight(right: AVLNode): void { + this.right = right; + } + + public getLeftHeight(): number { + return this.leftHeight; + } + + public setLeftHeight(leftHeight: number): void { + this.leftHeight = leftHeight; + } + + public getRightHeight(): number { + return this.rightHeight; + } + + public setRightHeight(rightHeight: number): void { + this.rightHeight = rightHeight; + } + + public getValue(): number { + return this.value; + } + public getParent(): AVLNode { + return this.parent; + } + + public setParent(parent: AVLNode): void { + this.parent = parent; + } + + /* #endregion */ + +} \ No newline at end of file diff --git a/src/avl-tree/avl.ts b/src/avl-tree/avl.ts new file mode 100644 index 0000000..684a000 --- /dev/null +++ b/src/avl-tree/avl.ts @@ -0,0 +1,72 @@ +import { AVLNode } from "./avl-node"; + +export interface IAVLTree { + insert(node: AVLNode): boolean + search(node: AVLNode): boolean + delete(node: AVLNode): boolean +} + +export class AVLTree implements IAVLTree { + constructor(private root: AVLNode = null) { } + + public insert(node: AVLNode, root: AVLNode = this.root): boolean { + if (root == null) { + this.root = node; + return true; + } + + if (node.compareTo(root) === 0) { + return false; + } else if (node.compareTo(root) > 0) { + if (root.getRight() == null) { + root.setRight(node); + this.updateNodeAfterInsert(node, root); + return true; + } else { + return this.insert(node, root.getRight()); + } + } else { + if (root.getLeft() == null) { + root.setLeft(node); + this.updateNodeAfterInsert(node, root); + return true; + } else { + return this.insert(node, root.getLeft()); + } + } + } + + public search(node: AVLNode): boolean { + // TODO: Implement search + return true; + } + public delete(node: AVLNode): boolean { + // TODO: Implement delete + return true; + } + + public getRoot(): AVLNode { + return this.root; + } + + private updateNodeAfterInsert(node: AVLNode, parent: AVLNode): void { + node.setParent(parent); + node.setNodeHeights(); + parent.setNodeHeights() + } + private detectRotation(node: AVLNode): void { + // TODO: add logic + } + private leftRotation(node: AVLNode): void { + // TODO: add logic + } + private rightRotation(node: AVLNode): void { + // TODO: add logic + } + private leftRightRotation(node: AVLNode): void { + // TODO: add logic + } + private rightLeftRotation(node: AVLNode): void { + // TODO: add logic + } +} \ No newline at end of file diff --git a/src/binary-search-tree/avl-tree.js b/src/binary-search-tree/avl-tree.js new file mode 100644 index 0000000..1061881 --- /dev/null +++ b/src/binary-search-tree/avl-tree.js @@ -0,0 +1,78 @@ +import { BST, BSTNode } from './binary-search-tree'; + +export class AVLNode extends BSTNode { + constructor(key, details = null, left = null, right = null, height = 0) { + super(key, details, left, right); + this._height = height; + } + + get height() { return this._height; } + set height(h) { this._height = h; } +} + +export const ROTATION = { + LEFT_LEFT: 'll', + LEFT_RIGHT: 'lr', + RIGHT_RIGHT: 'rr', + RIGHT_LEFT: 'rl' +}; + +export class AVLTree extends BST { + insert(el, root = this.root) { + + if (this.root === null) { + const node = new AVLNode(el); + this.root = node; + this.len = ++this.len; + return; + } + + if (root === null) { + this.len = ++this.len; + return new AVLNode(el); + } + + if (el < root.key) { + root.left = this.insert(el, root.left); + } else if (el > root.key) { + root.right = this.insert(el, root.right); + } else { + return root; + } + + root.height = this.height(root); + const balanceFactor = this.getBalanceState(root); + + if (balanceFactor > 1 || balanceFactor < -1) { + this.rebalance(node); + } + } + + rebalance(node) { + if (node.left.height - node.right.height > 1) { + if (this.height(node.left.left) > this.height(node.left.right)) { + this.rightRotate(node); + } else { + this.leftRightRotate(node); + } + } else { + if (this.height(node.right.left) > this.height(node.right.right)) { + this.leftRotate(node); + } else { + this.rightLeftRotate(node); + } + } + } + + leftRotate(node) { } + rightRotate(node) { } + leftRightRotate(node) { } + rightLeftRotate(node) { } + + getBalanceState(node) { + const leftHeight = node.left ? node.left.height : 0; + const rightHeight = node.right ? node.right.height : 0; + + return leftHeight - rightHeight; + } +} \ No newline at end of file diff --git a/src/binary-search-tree/avl-tree.spec.js b/src/binary-search-tree/avl-tree.spec.js new file mode 100644 index 0000000..75bf523 --- /dev/null +++ b/src/binary-search-tree/avl-tree.spec.js @@ -0,0 +1,34 @@ +import { AVLTree } from './avl-tree'; + +describe('AVL Tree', () => { + let avlTree; + beforeEach(() => { + avlTree = new AVLTree; + }); + + it('should create an avl tree', () => { + expect(avlTree).toBeDefined(); + expect(avlTree.len).toBe(0); + expect(avlTree.root).toBe(null); + }); + + it('should insert nodes in the tree', () => { + avlTree.insert(5); + expect(avlTree.len).toBe(1); + expect(avlTree.root.key).toBe(5); + expect(avlTree.root.height).toBe(0); + + avlTree.insert(9); + expect(avlTree.len).toBe(2); + expect(avlTree.root.right.key).toBe(9); + expect(avlTree.root.right.height).toBe(0); + expect(avlTree.root.height).toBe(1); + + avlTree.insert(9); + expect(avlTree.len).toBe(2); + expect(avlTree.root.right.key).toBe(9); + expect(avlTree.root.right.height).toBe(0); + expect(avlTree.root.height).toBe(1); + }); +}); + diff --git a/src/binary-search-tree/binary-search-tree.ts b/src/binary-search-tree/binary-search-tree.ts index d93e2f6..f26b4fe 100644 --- a/src/binary-search-tree/binary-search-tree.ts +++ b/src/binary-search-tree/binary-search-tree.ts @@ -67,10 +67,7 @@ const preOrderTraversal = Symbol("preorder"); const postOrderTraversal = Symbol("postorder"); const levelOrderTraversal = Symbol("levelorder"); -/** - * Private properties name - */ -const length = Symbol("length"); + /** * Binary Search Tree @@ -83,8 +80,10 @@ export class BST { this.root = null; this.length = 0; } - - get len() { + set len(l) { + this.length = l; + } + get len(): number { return this.length; }