diff --git a/README.md b/README.md index 245b07b6..cade6a1c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ * [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) + * [Quick Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/quick-sort) ## Running Tests diff --git a/src/algorithms/sorting/quick-sort/QuickSort.js b/src/algorithms/sorting/quick-sort/QuickSort.js new file mode 100644 index 00000000..d97e2897 --- /dev/null +++ b/src/algorithms/sorting/quick-sort/QuickSort.js @@ -0,0 +1,44 @@ +import Sort from '../Sort'; + +export default class QuickSort extends Sort { + sort(originalArray) { + // Clone original array to prevent it from modification. + const array = originalArray.slice(0); + + // If array has less then or equal to one elements then it is already sorted. + if (array.length <= 1) { + return array; + } + + // Init left and right arrays. + const leftArray = []; + const rightArray = []; + + // Take the first element of array as a pivot. + const pivotElement = array.shift(); + const centerArray = [pivotElement]; + + // Split all array elements between left, center and right arrays. + while (array.length) { + const currentElement = array.shift(); + + // Call visiting callback. + this.callbacks.visitingCallback(currentElement); + + if (this.comparator.equal(currentElement, pivotElement)) { + centerArray.push(currentElement); + } else if (this.comparator.lessThen(currentElement, pivotElement)) { + leftArray.push(currentElement); + } else { + rightArray.push(currentElement); + } + } + + // Sort left and right arrays. + const leftArraySorted = this.sort(leftArray); + const rightArraySorted = this.sort(rightArray); + + // Let's now join sorted left array with center array and with sorted right array. + return leftArraySorted.concat(centerArray, rightArraySorted); + } +} diff --git a/src/algorithms/sorting/quick-sort/README.md b/src/algorithms/sorting/quick-sort/README.md new file mode 100644 index 00000000..6ec8ca5e --- /dev/null +++ b/src/algorithms/sorting/quick-sort/README.md @@ -0,0 +1,28 @@ +# Quicksort + +Quicksort is a divide and conquer algorithm. +Quicksort first divides a large array into two smaller +sub-arrays: the low elements and the high elements. +Quicksort can then recursively sort the sub-arrays + +The steps are: + +1. Pick an element, called a pivot, from the array. +2. Partitioning: reorder the array so that all elements with +values less than the pivot come before the pivot, while all +elements with values greater than the pivot come after it +(equal values can go either way). After this partitioning, +the pivot is in its final position. This is called the +partition operation. +3. Recursively apply the above steps to the sub-array of +elements with smaller values and separately to the +sub-array of elements with greater values. + +Animated visualization of the quicksort algorithm. +The horizontal lines are pivot values. + +![Quicksort](https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif) + +## References + +[Wikipedia](https://en.wikipedia.org/wiki/Quicksort) diff --git a/src/algorithms/sorting/quick-sort/__test__/QuickSort.test.js b/src/algorithms/sorting/quick-sort/__test__/QuickSort.test.js new file mode 100644 index 00000000..0acca3e1 --- /dev/null +++ b/src/algorithms/sorting/quick-sort/__test__/QuickSort.test.js @@ -0,0 +1,60 @@ +import QuickSort from '../QuickSort'; +import { + equalArr, + notSortedArr, + reverseArr, + sortedArr, + SortTester, +} from '../../SortTester'; + +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 190; +const NOT_SORTED_ARRAY_VISITING_COUNT = 62; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 190; +const EQUAL_ARRAY_VISITING_COUNT = 19; + +describe('QuickSort', () => { + it('should sort array', () => { + SortTester.testSort(QuickSort); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(QuickSort); + }); + + it('should do stable sorting', () => { + SortTester.testSortStability(QuickSort); + }); + + it('should visit EQUAL array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSort, + equalArr, + EQUAL_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSort, + sortedArr, + SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit NOT SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSort, + notSortedArr, + NOT_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit REVERSE SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSort, + reverseArr, + REVERSE_SORTED_ARRAY_VISITING_COUNT, + ); + }); +});