Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DEVELOPMENT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This document outlines possible improvements and future developments for the `TS

- **Graph**: Implement a basic graph structure with common algorithms such as BFS, DFS, and shortest path algorithms. *(Implemented)*
- **PriorityQueue**: Provide a priority queue implementation for efficient task scheduling. *(Implemented)*
- **Balanced Trees**: Add self-balancing tree variants (e.g., AVL or Red-Black trees) for more efficient search and insert operations.
- **Balanced Trees**: Add self-balancing tree variants (e.g., AVL or Red-Black trees) for more efficient search and insert operations. *(Implemented)*

## Documentation

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,20 @@ g.addEdge(1, 3);
const path = g.shortestPath(1, 3);
```

## AVLTree<T>

`AVLTree` maintains balance automatically after each insertion to keep
operations fast.

```typescript
const avl = new AVLTree<number>();
avl.insert(1);
avl.insert(2);
avl.insert(3); // tree rotates, keeping 2 as the root
avl.contains(3); // true
avl.toArray(); // [1, 2, 3]
```

## BinaryTree<T>

#### getRoot(): BinaryTreeNode<T>
Expand Down
167 changes: 167 additions & 0 deletions src/AVLTree/AVLTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
export class AVLTreeNode<T> {
private value: T;
private left: AVLTreeNode<T> | null = null;
private right: AVLTreeNode<T> | null = null;
private height: number = 1;

constructor(value: T) {
this.value = value;
}

getValue(): T {
return this.value;
}

setValue(val: T): void {
this.value = val;
}

getLeft(): AVLTreeNode<T> | null {
return this.left;
}

setLeft(node: AVLTreeNode<T> | null): void {
this.left = node;
}

getRight(): AVLTreeNode<T> | null {
return this.right;
}

setRight(node: AVLTreeNode<T> | null): void {
this.right = node;
}

getHeight(): number {
return this.height;
}

setHeight(h: number): void {
this.height = h;
}
}

export class AVLTree<T> {
private root: AVLTreeNode<T> | null = null;

getRoot(): AVLTreeNode<T> | null {
return this.root;
}

insert(value: T): void {
this.root = this.insertNode(this.root, value);
}

contains(value: T): boolean {
return this.findNode(this.root, value) !== null;
}

toArray(): T[] {
const result: T[] = [];
this.inOrder(this.root, result);
return result;
}

private insertNode(node: AVLTreeNode<T> | null, value: T): AVLTreeNode<T> {
if (node === null) {
return new AVLTreeNode<T>(value);
}

if (value < node.getValue()) {
node.setLeft(this.insertNode(node.getLeft(), value));
} else if (value > node.getValue()) {
node.setRight(this.insertNode(node.getRight(), value));
} else {
return node; // no duplicates
}

this.updateHeight(node);
return this.balance(node);
}

private findNode(node: AVLTreeNode<T> | null, value: T): AVLTreeNode<T> | null {
if (node === null) {
return null;
}
if (value === node.getValue()) {
return node;
}
if (value < node.getValue()) {
return this.findNode(node.getLeft(), value);
}
return this.findNode(node.getRight(), value);
}

private inOrder(node: AVLTreeNode<T> | null, arr: T[]): void {
if (node === null) {
return;
}
this.inOrder(node.getLeft(), arr);
arr.push(node.getValue());
this.inOrder(node.getRight(), arr);
}

private heightOf(node: AVLTreeNode<T> | null): number {
return node ? node.getHeight() : 0;
}

private updateHeight(node: AVLTreeNode<T>): void {
node.setHeight(Math.max(this.heightOf(node.getLeft()), this.heightOf(node.getRight())) + 1);
}

private balanceFactor(node: AVLTreeNode<T>): number {
return this.heightOf(node.getLeft()) - this.heightOf(node.getRight());
}

private balance(node: AVLTreeNode<T>): AVLTreeNode<T> {
const balance = this.balanceFactor(node);

if (balance > 1) {
if (this.balanceFactor(node.getLeft()) < 0) {
node.setLeft(this.rotateLeft(node.getLeft()));
}
return this.rotateRight(node);
}

if (balance < -1) {
if (this.balanceFactor(node.getRight()) > 0) {
node.setRight(this.rotateRight(node.getRight()));
}
return this.rotateLeft(node);
}

return node;
}

private rotateLeft(x: AVLTreeNode<T>): AVLTreeNode<T> {
const y = x.getRight();
if (!y) {
return x;
}
const T2 = y.getLeft();

y.setLeft(x);
x.setRight(T2);

this.updateHeight(x);
this.updateHeight(y);

return y;
}

private rotateRight(y: AVLTreeNode<T>): AVLTreeNode<T> {
const x = y.getLeft();
if (!x) {
return y;
}
const T2 = x.getRight();

x.setRight(y);
y.setLeft(T2);

this.updateHeight(y);
this.updateHeight(x);

return x;
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './PriorityQueue/PriorityQueue';
export * from './Nodes/BinaryTreeNode';
export * from './BinaryTree/BinaryTree';
export * from './Graph/Graph';
export * from './AVLTree/AVLTree';
29 changes: 29 additions & 0 deletions tests/AVLTree/AVLTree.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect } from 'chai';
import { AVLTree } from '../../src/AVLTree/AVLTree';

describe('AVLTree', () => {
it('should keep tree balanced for ascending inserts', () => {
const tree = new AVLTree<number>();
tree.insert(1);
tree.insert(2);
tree.insert(3);

const root = tree.getRoot();
expect(root.getValue()).to.equal(2);
expect(root.getLeft().getValue()).to.equal(1);
expect(root.getRight().getValue()).to.equal(3);
});

it('should contain inserted values', () => {
const tree = new AVLTree<number>();
[10, 5, 15, 3, 8].forEach(v => tree.insert(v));
expect(tree.contains(8)).to.be.true;
expect(tree.contains(100)).to.be.false;
});

it('toArray should return values in order', () => {
const tree = new AVLTree<number>();
[4, 2, 6, 1, 3, 5, 7].forEach(v => tree.insert(v));
expect(tree.toArray()).to.deep.equal([1,2,3,4,5,6,7]);
});
});