diff --git a/src/algorithms/sorting/quick-sort/QuickSortInPlace.js b/src/algorithms/sorting/quick-sort/QuickSortInPlace.js index cc1f5e5068..b4b559f27e 100644 --- a/src/algorithms/sorting/quick-sort/QuickSortInPlace.js +++ b/src/algorithms/sorting/quick-sort/QuickSortInPlace.js @@ -4,7 +4,9 @@ export default class QuickSortInPlace extends Sort { /** Sorting in place avoids unnecessary use of additional memory, but modifies input array. * * This process is difficult to describe, but much clearer with a visualization: - * @see: http://www.algomation.com/algorithm/quick-sort-visualization + @see: https://www.hackerearth.com/practice/algorithms/sorting/quick-sort/visualize/ + * put the array size and the elements and on click of the button the visualization appears + * here is the youtube video @see: https://www.youtube.com/watch?v=aXXWXz5rF64&ab_channel=udiprod * * @param {*[]} originalArray - Not sorted array. * @param {number} inputLowIndex diff --git a/src/algorithms/sorting/tim-sort/README.md b/src/algorithms/sorting/tim-sort/README.md new file mode 100644 index 0000000000..7047fa889e --- /dev/null +++ b/src/algorithms/sorting/tim-sort/README.md @@ -0,0 +1,37 @@ +# Timsort + + +Timsort is a sorting algorithm based on insertion sort and merge sort. +it was invented by Peter Tim in 2002 and this sort is used in the +Java's Array.sort() method as well as Python language's sorted() and sort() method, +in this algorithm first we make the chunks of the array and sort +them using Insertion Sort and then we merge those sorted chunks using +merge of Merge Sort. + +The steps are: + +1. We divide the array into the blocks of Run +2. Those divided blocks are then sorted using the insertion +sort one by one +3. Once these are sorted we merge the sorted array chunks and combine +them using the merge sort. + +The size of the run can be 32 or 64 based on the array size, if those are power of two that +will help us in the performance in the insertion sort + +Animated visualization of the timesort algorithm. + + + +## Complexity + +| Name | Best | Average | Worst | Memory | Stable | Comments | +| --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | +| **Tim sort** | n O(n) | n n*log(n) | n n*log(n) | log(n) | Yes | Timsort requires space of O(n) | + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Timsort) +- [YouTube] [Part1](https://www.youtube.com/watch?v=emeME__917E) + [Part2](https://www.youtube.com/watch?v=6DOhQyqAAvU) + [Part3](https://www.youtube.com/watch?v=Yk4CBisILaw) diff --git a/src/algorithms/sorting/tim-sort/TimSort.js b/src/algorithms/sorting/tim-sort/TimSort.js new file mode 100644 index 0000000000..f68215deb3 --- /dev/null +++ b/src/algorithms/sorting/tim-sort/TimSort.js @@ -0,0 +1,133 @@ +import Sort from '../Sort'; +// import InsertionSort from '../insertion-sort/InsertionSort'; + +export default class TimSort extends Sort { + /** + * @name sort + * @description performs sorting using TimSort algorithm + * @param {*[]} originalArray + * @return {*[]} + */ + + sort(originalArray) { + const RUN = 32; + // clone the array so that original is intact + // eslint-disable-next-line prefer-const + let array = [...originalArray]; + + // const lengthOfArray = array.length; + + // if array has only one or zero element it is already sorted + if (array.length <= 1) { + return array; + } + + // perform insertion sort on the array first + for (let index = 0; index < array.length; index += RUN) { + // this.array=new this.insertionSort(). + this.array = this.insertionSort( + array, + index, + this.minValue(index + RUN - 1, array.length - 1), + ); + } + + // now merge the sorted chunks + for (let size = RUN; size < array.length; size *= 2) { + for (let left = 0; left < array.length; left += 2 * size) { + const mid = left + size - 1; + const right = this.minValue(left + 2 * size - 1, array.length - 1); + + if (this.comparator.lessThan(mid, right)) { + this.merge(this.array, left, mid, right); + } + } + } + + return this.array; + } + + /** + * @description returns a minimum value + * @param {*} a + * @param {*} b + * @returns {*} minimum value of the two + */ + minValue(a, b) { + return this.comparator.lessThan(a, b) ? a : b; + } + + /** + * @description merge the sorted array (chunks of array) + * @param {*[]} array + * @param {*} left + * @param {*} mid + * @param {*} right + */ + merge(array, left, mid, right) { + const localArray = [...array]; + const leftLength = mid - left + 1; + const rightLength = right - mid; + const leftArray = []; + const rightArray = []; + for (let index = 0; index < leftLength; index += 1) { + leftArray[index] = array[left + index]; + } + + for (let index = 0; index < rightLength; index += 1) { + rightArray[index] = array[mid + 1 + index]; + } + + let i = 0; + let j = 0; + let k = left; + + while (this.comparator.lessThan(i, leftLength) && this.comparator.lessThan(j, rightLength)) { + if (this.comparator.lessThanOrEqual(leftArray[i], rightArray[j])) { + localArray[k] = leftArray[i]; + i += 1; + } else { + localArray[k] = rightArray[j]; + } + } + while (this.comparator.lessThan(i, leftLength)) { + localArray[k] = rightArray[i]; + k += 1; + i += 1; + } + while (this.comparator.lessThan(j, rightLength)) { + localArray[k] = rightArray[j]; + k += 1; + j += 1; + } + // eslint-disable-next-line no-param-reassign + array = Array.from(localArray); + } + + /** + * @description perform the insertion sort on the array chunks + * @param {*[]} array + * @param {*} left + * @param {*} right + * @returns {*[]} + */ + insertionSort(array, left, right) { + const localArray = [...array]; + + for (let index = left + 1; index <= right; index += 1) { + const element = localArray[index]; + this.callbacks.visitingCallback(element); + let j = index - 1; + while (this.comparator.greaterThanOrEqual(j, left) + && this.comparator.greaterThan(localArray[j], element)) { + localArray[j + 1] = localArray[j]; + j -= 1; + if (j < 0) break; + } + localArray[j + 1] = element; + } + // eslint-disable-next-line no-param-reassign + array = localArray; + return array; + } +} diff --git a/src/algorithms/sorting/tim-sort/__test__/TimSort.test.js b/src/algorithms/sorting/tim-sort/__test__/TimSort.test.js new file mode 100644 index 0000000000..98eb377f08 --- /dev/null +++ b/src/algorithms/sorting/tim-sort/__test__/TimSort.test.js @@ -0,0 +1,64 @@ +import TimSort from '../TimSort'; +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('TimSort', () => { + it('should sort array', () => { + SortTester.testSort(TimSort); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(TimSort); + }); + + it('should do stable sorting', () => { + SortTester.testSortStability(TimSort); + }); + + it('should sort negative numbers', () => { + SortTester.testNegativeNumbersSort(TimSort); + }); + + it('should visit EQUAL array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + TimSort, + equalArr, + EQUAL_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + TimSort, + sortedArr, + SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit NOT SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + TimSort, + notSortedArr, + NOT_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit REVERSE SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + TimSort, + reverseArr, + REVERSE_SORTED_ARRAY_VISITING_COUNT, + ); + }); +});