diff --git a/README.md b/README.md index c088af6e..245b07b6 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ * [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) + * [Merge Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/merge-sort) ## Running Tests diff --git a/src/algorithms/sorting/SortTester.js b/src/algorithms/sorting/SortTester.js index 7853ac69..725793e0 100644 --- a/src/algorithms/sorting/SortTester.js +++ b/src/algorithms/sorting/SortTester.js @@ -32,6 +32,7 @@ export class SortTester { expect(sorter.sort([''])).toEqual(['']); expect(sorter.sort(['a'])).toEqual(['a']); expect(sorter.sort(['aa', 'a'])).toEqual(['a', 'aa']); + expect(sorter.sort(['aa', 'q', 'a', 'bbbb', 'ccc'])).toEqual(['q', 'a', 'aa', 'ccc', 'bbbb']); expect(sorter.sort(['aa', 'aa'])).toEqual(['aa', 'aa']); } diff --git a/src/algorithms/sorting/merge-sort/MergeSort.js b/src/algorithms/sorting/merge-sort/MergeSort.js new file mode 100644 index 00000000..d40679ff --- /dev/null +++ b/src/algorithms/sorting/merge-sort/MergeSort.js @@ -0,0 +1,59 @@ +import Sort from '../Sort'; + +export default class MergeSort extends Sort { + sort(originalArray) { + // Call visiting callback. + this.callbacks.visitingCallback(null); + + // If array is empty or consists of one element then return this array since it is sorted. + if (originalArray.length <= 1) { + return originalArray; + } + + // Split array on two halves. + const middleIndex = Math.floor(originalArray.length / 2); + const leftArray = originalArray.slice(0, middleIndex); + const rightArray = originalArray.slice(middleIndex, originalArray.length); + + // Sort two halves of split array + const leftSortedArray = this.sort(leftArray); + const rightSortedArray = this.sort(rightArray); + + // Merge two sorted arrays into one. + return this.mergeSortedArrays(leftSortedArray, rightSortedArray); + } + + mergeSortedArrays(leftArray, rightArray) { + let sortedArray = []; + + // In case if arrays are not of size 1. + while (leftArray.length && rightArray.length) { + let minimumElement = null; + + // Find minimum element of two arrays. + if (this.comparator.lessThenOrEqual(leftArray[0], rightArray[0])) { + minimumElement = leftArray.shift(); + } else { + minimumElement = rightArray.shift(); + } + + // Call visiting callback. + this.callbacks.visitingCallback(minimumElement); + + // Push the minimum element of two arrays to the sorted array. + sortedArray.push(minimumElement); + } + + // If one of two array still have elements we need to just concatenate + // this element to the sorted array since it is already sorted. + if (leftArray.length) { + sortedArray = sortedArray.concat(leftArray); + } + + if (rightArray.length) { + sortedArray = sortedArray.concat(rightArray); + } + + return sortedArray; + } +} diff --git a/src/algorithms/sorting/merge-sort/README.md b/src/algorithms/sorting/merge-sort/README.md new file mode 100644 index 00000000..00d6b909 --- /dev/null +++ b/src/algorithms/sorting/merge-sort/README.md @@ -0,0 +1,27 @@ +# Merge Sort + +In computer science, merge sort (also commonly spelled +mergesort) is an efficient, general-purpose, +comparison-based sorting algorithm. Most implementations +produce a stable sort, which means that the implementation +preserves the input order of equal elements in the sorted +output. Mergesort is a divide and conquer algorithm that +was invented by John von Neumann in 1945. + +An example of merge sort. First divide the list into +the smallest unit (1 element), then compare each +element with the adjacent list to sort and merge the +two adjacent lists. Finally all the elements are sorted +and merged. + +![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif) + +A recursive merge sort algorithm used to sort an array of 7 +integer values. These are the steps a human would take to +emulate merge sort (top-down). + +![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/e/e6/Merge_sort_algorithm_diagram.svg) + +## References + +[Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) diff --git a/src/algorithms/sorting/merge-sort/__test__/MergeSort.test.js b/src/algorithms/sorting/merge-sort/__test__/MergeSort.test.js new file mode 100644 index 00000000..4ae380be --- /dev/null +++ b/src/algorithms/sorting/merge-sort/__test__/MergeSort.test.js @@ -0,0 +1,60 @@ +import BubbleSort from '../MergeSort'; +import { + equalArr, + notSortedArr, + reverseArr, + sortedArr, + SortTester, +} from '../../SortTester'; + +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 79; +const NOT_SORTED_ARRAY_VISITING_COUNT = 102; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 87; +const EQUAL_ARRAY_VISITING_COUNT = 79; + +describe('MergeSort', () => { + it('should sort array', () => { + SortTester.testSort(BubbleSort); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(BubbleSort); + }); + + // it('should do stable sorting', () => { + // SortTester.testSortStability(BubbleSort); + // }); + + it('should visit EQUAL array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + BubbleSort, + equalArr, + EQUAL_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + BubbleSort, + sortedArr, + SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit NOT SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + BubbleSort, + notSortedArr, + NOT_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit REVERSE SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + BubbleSort, + reverseArr, + REVERSE_SORTED_ARRAY_VISITING_COUNT, + ); + }); +});