diff --git a/src/algorithms/sorting/quick-sort/QuickSortIterative.js b/src/algorithms/sorting/quick-sort/QuickSortIterative.js new file mode 100644 index 00000000..46bcc8b6 --- /dev/null +++ b/src/algorithms/sorting/quick-sort/QuickSortIterative.js @@ -0,0 +1,96 @@ +import Sort from '../Sort'; +import Stack from '../../../data-structures/stack/Stack'; + +export default class QuickSortIterative extends Sort { + /** + * Iterative Quick Sort + * + * @param {*[]} originalArray - Not sorted array. + * @param {number} inputLowIndex + * @param {number} inputHighIndex + * @return {*[]} - Sorted array. + */ + sort( + originalArray, + inputLowIndex = 0, + inputHighIndex = originalArray.length - 1, + sortInPlace = true, + ) { + // Copies array on in case we don't want to sort in place + const array = sortInPlace ? originalArray : [...originalArray]; + + // If array has less than or equal to one elements then it is already sorted. + if (array.length <= 1) { + return array; + } + + /** + * The partitionArray() operates on the subarray between lowIndex and highIndex, inclusive. + * It arbitrarily chooses the last element in the subarray as the pivot. + * Then, it partially sorts the subarray into elements than are less than the pivot, + * and elements that are greater than or equal to the pivot. + * Each time partitionArray() is executed, the pivot element is in its final sorted position. + * + * @param {number} lowIndex + * @param {number} highIndex + * @return {number} + */ + const partitionArray = (lowIndex, highIndex) => { + /** + * Swaps two elements in array. + * @param {number} leftIndex + * @param {number} rightIndex + */ + const swap = (leftIndex, rightIndex) => { + const temp = array[leftIndex]; + array[leftIndex] = array[rightIndex]; + array[rightIndex] = temp; + }; + + const pivot = array[highIndex]; + // visitingCallback is used for time-complexity analysis. + this.callbacks.visitingCallback(array[pivot]); + + let partitionIndex = lowIndex; + for (let currentIndex = lowIndex; currentIndex < highIndex; currentIndex += 1) { + if (this.comparator.lessThan(array[currentIndex], pivot)) { + swap(partitionIndex, currentIndex); + partitionIndex += 1; + } + } + + // The element at the partitionIndex is guaranteed to be greater than or equal to pivot. + // All elements to the left of partitionIndex are guaranteed to be less than pivot. + // Swapping the pivot with the partitionIndex therefore places the pivot in its + // final sorted position. + swap(partitionIndex, highIndex); + + return partitionIndex; + }; + + /** + * Replace recursion with auxiliary stack + */ + const stack = new Stack(); + stack.push(inputLowIndex); + stack.push(inputHighIndex); + + while (!stack.isEmpty()) { + const highIndex = stack.pop(); + const lowIndex = stack.pop(); + const partitionIndex = partitionArray(lowIndex, highIndex); + + if (partitionIndex - 1 > lowIndex) { + stack.push(lowIndex); + stack.push(partitionIndex - 1); + } + + if (partitionIndex + 1 < highIndex) { + stack.push(partitionIndex + 1); + stack.push(highIndex); + } + } + + return array; + } +} diff --git a/src/algorithms/sorting/quick-sort/__test__/QuickSortIterative.test.js b/src/algorithms/sorting/quick-sort/__test__/QuickSortIterative.test.js new file mode 100644 index 00000000..b68427a8 --- /dev/null +++ b/src/algorithms/sorting/quick-sort/__test__/QuickSortIterative.test.js @@ -0,0 +1,74 @@ +import QuickSortIterative from '../QuickSortIterative'; +import { + equalArr, + notSortedArr, + reverseArr, + sortedArr, + SortTester, +} from '../../SortTester'; + +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 19; +const NOT_SORTED_ARRAY_VISITING_COUNT = 19; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 19; +const EQUAL_ARRAY_VISITING_COUNT = 19; + +describe('QuickSortIterative', () => { + it('should sort array', () => { + SortTester.testSort(QuickSortIterative); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(QuickSortIterative); + }); + + it('should sort negative numbers', () => { + SortTester.testNegativeNumbersSort(QuickSortIterative); + }); + + it('should visit EQUAL array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortIterative, + equalArr, + EQUAL_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortIterative, + sortedArr, + SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit NOT SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortIterative, + notSortedArr, + NOT_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit REVERSE SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortIterative, + reverseArr, + REVERSE_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should sort in place', () => { + const sorter = new QuickSortIterative(); + const originalArray = [7, 5, 1, 42, 30, 24, 14]; + const sortedArray = sorter.sort(originalArray); + expect(originalArray).toEqual(sortedArray); + }); + + it('should not modify original array', () => { + const sorter = new QuickSortIterative(); + const originalArray = [7, 5, 1, 42, 30, 24, 14]; + const sortedArray = sorter.sort(originalArray, 0, originalArray.length - 1, false); + expect(originalArray).not.toEqual(sortedArray); + }); +});