Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ⚡ improved build heap performance from nlogn to n #38

Merged
merged 1 commit into from
Dec 13, 2024
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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ import {
BiDirectionalMap,
LRUCache,
} from 'jsr:@mskr/data-structures';
// or if you want to use it with esm.sh
```

### Using directly without installing the package with esm.sh

```typescript
// import * as ds from 'https://esm.sh/jsr/@mskr/data-structures';
// import { ... } from 'https://esm.sh/jsr/@mskr/data-structures';
```

[**_Demo_**](https://codepen.io/mandy8055/pen/ZYzBpjL)

## Available Data Structures and their detailed documentations

- [LinkedList](./docs/linked-list.md): Singly linked list implementation
Expand Down
18 changes: 18 additions & 0 deletions docs/binary-heap.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ minHeap.insert({ name: 'Charlie', age: 30 });
console.log(minHeap.peek()); // { name: "Bob", age: 20 }
```

### Heap Construction

```typescript
// Create a heap with initial elements O(n)
const minHeap = new MinHeap<number>(null, [5, 3, 8, 1, 7]);
console.log(minHeap.peek()); // 1 (minimum element)
console.log(minHeap.size); // 5
// Similarly for MaxHeap
const maxHeap = new MaxHeap<number>(null, [5, 3, 8, 1]);
console.log(maxHeap.peek()); // 8
console.log(maxHeap.size); // 4
```

### Type-Safe Comparable Objects

```typescript
Expand Down Expand Up @@ -140,6 +153,7 @@ try {
- Peek: O(1)
- Contains: O(n)
- Space complexity: O(n)
- Build Heap from Array: O(n)

## Implementation Details

Expand All @@ -149,6 +163,10 @@ The heap is implemented as a complete binary tree stored in an array, where for
- Right child is at index: 2i + 2
- Parent is at index: floor((i-1)/2)

## Efficient heap Initialization

When creating a heap with an initial array of elements, the construction is optimized to use an O(n) algorithm. This means that initializing a heap with an array is significantly more efficient than inserting elements one by one(takes O(n log n)), providing a performant way to create heaps from existing collections.

### Key Features

1. Generic type support with type safety
Expand Down
3 changes: 2 additions & 1 deletion docs/priority-queue.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ try {
- toArray: O(n)
- toSortedArray: O(n log n)
- Space complexity: O(n)
- Initialize Priority Queue with array: O(n)

## Implementation Details

Expand All @@ -179,7 +180,7 @@ The priority queue is implemented using a binary min-heap where:

1. Generic type support
2. Customizable priority ordering through comparator function
3. Optional initialization with existing values
3. Optional initialization with existing values in O(n)
4. Both heap-ordered and priority-ordered array conversions
5. Efficient priority-based operations
6. Iterator implementation for collection processing
57 changes: 32 additions & 25 deletions src/core/binary-heap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ import { compareNumbers, compareStrings } from '../utils/index.ts';
* console.log(minHeap.peek()); // 3
* console.log(minHeap.remove()); // 3
* ```
*
* @example
* ```typescript
* const minHeap = new MinHeap<number>(null, [5, 3, 8, 1]);
* console.log(minHeap.peek()); // 1
* console.log(minHeap.size); // 4
* // Similarly for MaxHeap
* const maxHeap = new MaxHeap<number>(null, [5, 3, 8, 1]);
* console.log(maxHeap.peek()); // 8
* console.log(maxHeap.size); // 4
* ```
*/
export abstract class BinaryHeap<T> implements Iterable<T> {
/**
Expand All @@ -47,10 +58,15 @@ export abstract class BinaryHeap<T> implements Iterable<T> {
/**
* Creates an empty binary heap
* @param comparator Optional custom comparison function
* @param initial Optional array of elements to initialize the heap
*/
constructor(comparator?: (a: T, b: T) => number) {
constructor(comparator?: (a: T, b: T) => number, initial?: T[]) {
this.heap = [];
this.compare = comparator || this.getDefaultComparator();

if (initial && initial.length > 0) {
this.buildHeap(initial);
}
}

/**
Expand Down Expand Up @@ -180,6 +196,19 @@ export abstract class BinaryHeap<T> implements Iterable<T> {
return Math.floor((index - 1) / 2);
}

/**
* @ignore
* Efficiently builds a heap from an initial array in O(n) time
* @protected
* @param initial Array of elements to build the heap from
*/
protected buildHeap(initial: T[]): void {
this.heap = [...initial];
for (let i = Math.floor(this.heap.length / 2) - 1; i >= 0; i--) {
this.siftDown(i);
}
}

/**
* @ignore
* Gets the left child index for a given parent index
Expand Down Expand Up @@ -216,18 +245,7 @@ export abstract class BinaryHeap<T> implements Iterable<T> {
/**
* MinHeap implementation where the root is always the minimum element.
*
* All operations inherited from {@link BinaryHeap} are available:
* @augments BinaryHeap<T>
*
* Methods available from BinaryHeap:
* - {@link BinaryHeap#size} - Returns the number of elements in the heap
* - {@link BinaryHeap#isEmpty} - Checks if the heap is empty
* - {@link BinaryHeap#peek} - Returns the minimum element without removing it
* - {@link BinaryHeap#insert} - Inserts a new element into the heap
* - {@link BinaryHeap#remove} - Removes and returns the minimum element
* - {@link BinaryHeap#contains} - Checks if an element exists in the heap
* - {@link BinaryHeap#clear} - Removes all elements from the heap
* - {@link BinaryHeap#toArray} - Converts the heap to an array
* All operations inherited from {@link BinaryHeap} are available.
*
* @template T The type of elements stored in the heap
*
Expand Down Expand Up @@ -311,18 +329,7 @@ export class MinHeap<T> extends BinaryHeap<T> {
/**
* MaxHeap implementation where the root is always the maximum element.
*
* All operations inherited from {@link BinaryHeap} are available:
* @augments BinaryHeap<T>
*
* Methods available from BinaryHeap:
* - {@link BinaryHeap#size} - Returns the number of elements in the heap
* - {@link BinaryHeap#isEmpty} - Checks if the heap is empty
* - {@link BinaryHeap#peek} - Returns the maximum element without removing it
* - {@link BinaryHeap#insert} - Inserts a new element into the heap
* - {@link BinaryHeap#remove} - Removes and returns the maximum element
* - {@link BinaryHeap#contains} - Checks if an element exists in the heap
* - {@link BinaryHeap#clear} - Removes all elements from the heap
* - {@link BinaryHeap#toArray} - Converts the heap to an array
* All operations inherited from {@link BinaryHeap} are available.
*
* @template T The type of elements stored in the heap
*
Expand Down
8 changes: 1 addition & 7 deletions src/core/priority-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,7 @@ export class PriorityQueue<T> implements Iterable<T> {
*/
initial?: T[];
}) {
this.heap = new MinHeap<T>(options?.comparator);

if (options?.initial) {
for (const value of options.initial) {
this.enqueue(value);
}
}
this.heap = new MinHeap<T>(options?.comparator, options?.initial);
}

/**
Expand Down
110 changes: 110 additions & 0 deletions src/tests/binary-heap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,113 @@ Deno.test('BinaryHeap - Coverage Enhancement Tests', async (t) => {
assertEquals(elements, [3, 7, 8, 9, 10]);
});
});

Deno.test('BinaryHeap - BuildHeap Method', async (t) => {
await t.step('should build MinHeap from initial array efficiently', () => {
const initialArray = [5, 3, 7, 1, 4];
const heap = new MinHeap<number>(undefined, initialArray);

// Verify size
assertEquals(heap.size, 5);

// Verify heap property (root should be minimum)
assertEquals(heap.peek(), 1);

// Verify contents
const elements = [];
while (!heap.isEmpty()) {
elements.push(heap.remove());
}
assertEquals(elements, [1, 3, 4, 5, 7]);
});

await t.step('should build MaxHeap from initial array efficiently', () => {
const initialArray = [5, 3, 7, 1, 4];
const heap = new MaxHeap<number>(undefined, initialArray);

// Verify size
assertEquals(heap.size, 5);

// Verify heap property (root should be maximum)
assertEquals(heap.peek(), 7);

// Verify contents
const elements = [];
while (!heap.isEmpty()) {
elements.push(heap.remove());
}
assertEquals(elements, [7, 5, 4, 3, 1]);
});

await t.step('should build heap with custom comparator', () => {
interface Person {
name: string;
priority: number;
}

const people: Person[] = [
{ name: 'Alice', priority: 3 },
{ name: 'Bob', priority: 1 },
{ name: 'Charlie', priority: 2 },
];

const heap = new MinHeap<Person>((a, b) => a.priority - b.priority, people);

// Verify size
assertEquals(heap.size, 3);

// Verify heap property
assertEquals(heap.peek().name, 'Bob');

// Remove elements and verify order
const removedPeople = [];
while (!heap.isEmpty()) {
removedPeople.push(heap.remove());
}
assertEquals(
removedPeople.map((p) => p.name),
['Bob', 'Charlie', 'Alice'],
);
});

await t.step('should handle empty initial array', () => {
const heap = new MinHeap<number>(undefined, []);

// Verify empty heap
assertEquals(heap.size, 0);
assertEquals(heap.isEmpty(), true);
assertThrows(() => heap.peek(), EmptyStructureError);
});

await t.step('should handle single element array', () => {
const heap = new MinHeap<number>(undefined, [42]);

// Verify heap properties
assertEquals(heap.size, 1);
assertEquals(heap.peek(), 42);
assertEquals(heap.remove(), 42);
assertEquals(heap.isEmpty(), true);
});

await t.step('should work with Comparable objects', () => {
const items = [
new ComparableItem(5),
new ComparableItem(3),
new ComparableItem(7),
new ComparableItem(1),
new ComparableItem(4),
];

const heap = new MinHeap<ComparableItem>(undefined, items);

// Verify heap property
assertEquals(heap.peek().getValue(), 1);

// Remove and verify order
const values = [];
while (!heap.isEmpty()) {
values.push(heap.remove().getValue());
}
assertEquals(values, [1, 3, 4, 5, 7]);
});
});
Loading