From 36bbfed6a1b363926684d711d0f5eab483aa18a4 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Sat, 14 Apr 2018 10:29:36 +0300 Subject: [PATCH] Add heap sort. --- README.md | 1 + .../bubble-sort/__test__/BubbleSort.test.js | 22 ++++---- src/algorithms/sorting/heap-sort/HeapSort.js | 30 ++++++++++ src/algorithms/sorting/heap-sort/README.md | 18 ++++++ .../heap-sort/__test__/HeapSort.test.js | 56 +++++++++++++++++++ .../__test__/InsertionSort.test.js | 22 ++++---- .../__test__/SelectionSort.test.js | 22 ++++---- src/data-structures/heap/MinHeap.js | 8 ++- .../heap/__test__/MinHeap.test.js | 2 + 9 files changed, 143 insertions(+), 38 deletions(-) create mode 100644 src/algorithms/sorting/heap-sort/HeapSort.js create mode 100644 src/algorithms/sorting/heap-sort/README.md create mode 100644 src/algorithms/sorting/heap-sort/__test__/HeapSort.test.js diff --git a/README.md b/README.md index 7f542344..c088af6e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ * [Bubble Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/bubble-sort) * [Selection Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/selection-sort) * [Insertion Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/insertion-sort) + * [Heap Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/heap-sort) ## Running Tests diff --git a/src/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js b/src/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js index 40a7a43f..8ab3984e 100644 --- a/src/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js +++ b/src/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js @@ -7,6 +7,12 @@ import { SortTester, } from '../../SortTester'; +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 20; +const NOT_SORTED_ARRAY_VISITING_COUNT = 280; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 400; +const EQUAL_ARRAY_VISITING_COUNT = 20; + describe('BubbleSort', () => { it('should sort array', () => { SortTester.testSort(BubbleSort); @@ -21,42 +27,34 @@ describe('BubbleSort', () => { }); it('should visit EQUAL array element specified number of times', () => { - const expectedNumberOfVisits = 20; - SortTester.testAlgorithmTimeComplexity( BubbleSort, equalArr, - expectedNumberOfVisits, + EQUAL_ARRAY_VISITING_COUNT, ); }); it('should visit SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 20; - SortTester.testAlgorithmTimeComplexity( BubbleSort, sortedArr, - expectedNumberOfVisits, + SORTED_ARRAY_VISITING_COUNT, ); }); it('should visit NOT SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 280; - SortTester.testAlgorithmTimeComplexity( BubbleSort, notSortedArr, - expectedNumberOfVisits, + NOT_SORTED_ARRAY_VISITING_COUNT, ); }); it('should visit REVERSE SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 400; - SortTester.testAlgorithmTimeComplexity( BubbleSort, reverseArr, - expectedNumberOfVisits, + REVERSE_SORTED_ARRAY_VISITING_COUNT, ); }); }); diff --git a/src/algorithms/sorting/heap-sort/HeapSort.js b/src/algorithms/sorting/heap-sort/HeapSort.js new file mode 100644 index 00000000..06e58e1e --- /dev/null +++ b/src/algorithms/sorting/heap-sort/HeapSort.js @@ -0,0 +1,30 @@ +import Sort from '../Sort'; +import MinHeap from '../../../data-structures/heap/MinHeap'; + +export default class HeapSort extends Sort { + sort(originalArray) { + const sortedArray = []; + const minHeap = new MinHeap(this.callbacks.compareCallback); + + // Insert all array elements to the heap. + originalArray.forEach((element) => { + // Call visiting callback. + this.callbacks.visitingCallback(element); + + minHeap.add(element); + }); + + // Now we have min heap with minimal element always on top. + // Let's poll that minimal element one by one and thus form the sorted array. + while (!minHeap.isEmpty()) { + const nextMinElement = minHeap.poll(); + + // Call visiting callback. + this.callbacks.visitingCallback(nextMinElement); + + sortedArray.push(nextMinElement); + } + + return sortedArray; + } +} diff --git a/src/algorithms/sorting/heap-sort/README.md b/src/algorithms/sorting/heap-sort/README.md new file mode 100644 index 00000000..7504875d --- /dev/null +++ b/src/algorithms/sorting/heap-sort/README.md @@ -0,0 +1,18 @@ +# Heap Sort + +Heapsort is a comparison-based sorting algorithm. +Heapsort can be thought of as an improved selection +sort: like that algorithm, it divides its input into +a sorted and an unsorted region, and it iteratively +shrinks the unsorted region by extracting the largest +element and moving that to the sorted region. The +improvement consists of the use of a heap data structure +rather than a linear-time search to find the maximum. + +![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/1/1b/Sorting_heapsort_anim.gif) + +![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/4/4d/Heapsort-example.gif) + +## References + +[Wikipedia](https://en.wikipedia.org/wiki/Heapsort) diff --git a/src/algorithms/sorting/heap-sort/__test__/HeapSort.test.js b/src/algorithms/sorting/heap-sort/__test__/HeapSort.test.js new file mode 100644 index 00000000..bc5a364e --- /dev/null +++ b/src/algorithms/sorting/heap-sort/__test__/HeapSort.test.js @@ -0,0 +1,56 @@ +import HeapSort from '../HeapSort'; +import { + equalArr, + notSortedArr, + reverseArr, + sortedArr, + SortTester, +} from '../../SortTester'; + +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 40; +const NOT_SORTED_ARRAY_VISITING_COUNT = 40; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 40; +const EQUAL_ARRAY_VISITING_COUNT = 40; + +describe('HeapSort', () => { + it('should sort array', () => { + SortTester.testSort(HeapSort); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(HeapSort); + }); + + it('should visit EQUAL array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + HeapSort, + equalArr, + EQUAL_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + HeapSort, + sortedArr, + SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit NOT SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + HeapSort, + notSortedArr, + NOT_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit REVERSE SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + HeapSort, + reverseArr, + REVERSE_SORTED_ARRAY_VISITING_COUNT, + ); + }); +}); diff --git a/src/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js b/src/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js index ba4fc9f2..4fb96e2f 100644 --- a/src/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js +++ b/src/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js @@ -7,6 +7,12 @@ import { SortTester, } from '../../SortTester'; +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 20; +const NOT_SORTED_ARRAY_VISITING_COUNT = 101; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 210; +const EQUAL_ARRAY_VISITING_COUNT = 20; + describe('InsertionSort', () => { it('should sort array', () => { SortTester.testSort(InsertionSort); @@ -21,42 +27,34 @@ describe('InsertionSort', () => { }); it('should visit EQUAL array element specified number of times', () => { - const expectedNumberOfVisits = 20; - SortTester.testAlgorithmTimeComplexity( InsertionSort, equalArr, - expectedNumberOfVisits, + EQUAL_ARRAY_VISITING_COUNT, ); }); it('should visit SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 20; - SortTester.testAlgorithmTimeComplexity( InsertionSort, sortedArr, - expectedNumberOfVisits, + SORTED_ARRAY_VISITING_COUNT, ); }); it('should visit NOT SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 101; - SortTester.testAlgorithmTimeComplexity( InsertionSort, notSortedArr, - expectedNumberOfVisits, + NOT_SORTED_ARRAY_VISITING_COUNT, ); }); it('should visit REVERSE SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 210; - SortTester.testAlgorithmTimeComplexity( InsertionSort, reverseArr, - expectedNumberOfVisits, + REVERSE_SORTED_ARRAY_VISITING_COUNT, ); }); }); diff --git a/src/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js b/src/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js index 856dd66d..4a56aa9c 100644 --- a/src/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js +++ b/src/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js @@ -7,6 +7,12 @@ import { SortTester, } from '../../SortTester'; +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 209; +const NOT_SORTED_ARRAY_VISITING_COUNT = 209; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; +const EQUAL_ARRAY_VISITING_COUNT = 209; + describe('SelectionSort', () => { it('should sort array', () => { SortTester.testSort(SelectionSort); @@ -17,42 +23,34 @@ describe('SelectionSort', () => { }); it('should visit EQUAL array element specified number of times', () => { - const expectedNumberOfVisits = 209; - SortTester.testAlgorithmTimeComplexity( SelectionSort, equalArr, - expectedNumberOfVisits, + EQUAL_ARRAY_VISITING_COUNT, ); }); it('should visit SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 209; - SortTester.testAlgorithmTimeComplexity( SelectionSort, sortedArr, - expectedNumberOfVisits, + SORTED_ARRAY_VISITING_COUNT, ); }); it('should visit NOT SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 209; - SortTester.testAlgorithmTimeComplexity( SelectionSort, notSortedArr, - expectedNumberOfVisits, + NOT_SORTED_ARRAY_VISITING_COUNT, ); }); it('should visit REVERSE SORTED array element specified number of times', () => { - const expectedNumberOfVisits = 209; - SortTester.testAlgorithmTimeComplexity( SelectionSort, reverseArr, - expectedNumberOfVisits, + REVERSE_SORTED_ARRAY_VISITING_COUNT, ); }); }); diff --git a/src/data-structures/heap/MinHeap.js b/src/data-structures/heap/MinHeap.js index 49d78528..6eb2d843 100644 --- a/src/data-structures/heap/MinHeap.js +++ b/src/data-structures/heap/MinHeap.js @@ -1,10 +1,10 @@ import Comparator from '../../utils/comparator/Comparator'; export default class MinHeap { - constructor() { + constructor(comparatorFunction) { // Array representation of the heap. this.heapContainer = []; - this.compare = new Comparator(); + this.compare = new Comparator(comparatorFunction); } static getLeftChildIndex(parentIndex) { @@ -120,6 +120,10 @@ export default class MinHeap { } } + isEmpty() { + return !this.heapContainer.length; + } + toString() { return this.heapContainer.toString(); } diff --git a/src/data-structures/heap/__test__/MinHeap.test.js b/src/data-structures/heap/__test__/MinHeap.test.js index 04fc06fc..6dc7c162 100644 --- a/src/data-structures/heap/__test__/MinHeap.test.js +++ b/src/data-structures/heap/__test__/MinHeap.test.js @@ -6,12 +6,14 @@ describe('MinHeap', () => { expect(minHeap).toBeDefined(); expect(minHeap.peek()).toBeNull(); + expect(minHeap.isEmpty()).toBeTruthy(); }); it('should add items to the heap and heapify it up', () => { const minHeap = new MinHeap(); minHeap.add(5); + expect(minHeap.isEmpty()).toBeFalsy(); expect(minHeap.peek()).toBe(5); expect(minHeap.toString()).toBe('5');