diff --git a/README.md b/README.md index 4abeb5b1..5873a212 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ * [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) + * [Quicksort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/quick-sort) + * [Shellsort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/shell-sort) ## Running Tests @@ -106,11 +107,12 @@ Below is the list of some of the most used Big O notations and their performance ### Array Sorting Algorithms Complexity -| Name | Best | Average | Worst | Memory | Stable | -| --------------------- | :-------: | :-------: | :-------: | :-------: | :-------: | -| **Bubble sort** | n | n^2 | n^2 | 1 | Yes | -| **Insertion sort** | n | n^2 | n^2 | 1 | Yes | -| **Selection sort** | n^2 | n^2 | n^2 | 1 | No | -| **Heap sort** | n log(n) | n log(n) | n log(n) | 1 | No | -| **Merge sort** | n log(n) | n log(n) | n log(n) | n | Yes | -| **Quick sort** | n log(n) | n log(n) | n^2 | log(n) | No | +| Name | Best | Average | Worst | Memory | Stable | +| --------------------- | :-------: | :-------: | :-----------: | :-------: | :-------: | +| **Bubble sort** | n | n^2 | n^2 | 1 | Yes | +| **Insertion sort** | n | n^2 | n^2 | 1 | Yes | +| **Selection sort** | n^2 | n^2 | n^2 | 1 | No | +| **Heap sort** | n log(n) | n log(n) | n log(n) | 1 | No | +| **Merge sort** | n log(n) | n log(n) | n log(n) | n | Yes | +| **Quick sort** | n log(n) | n log(n) | n^2 | log(n) | No | +| **Shell sort** | n log(n) | depends on gap sequence | n (log(n))^2 | 1 | No | diff --git a/src/algorithms/sorting/SortTester.js b/src/algorithms/sorting/SortTester.js index 725793e0..696f6e28 100644 --- a/src/algorithms/sorting/SortTester.js +++ b/src/algorithms/sorting/SortTester.js @@ -32,7 +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', 'q', 'bbbb', 'ccc'])).toEqual(['q', 'aa', 'ccc', 'bbbb']); expect(sorter.sort(['aa', 'aa'])).toEqual(['aa', 'aa']); } @@ -49,6 +49,7 @@ export class SortTester { const sorter = new SortingClass(callbacks); expect(sorter.sort(['bb', 'aa', 'c'])).toEqual(['c', 'bb', 'aa']); + expect(sorter.sort(['aa', 'q', 'a', 'bbbb', 'ccc'])).toEqual(['q', 'a', 'aa', 'ccc', 'bbbb']); } static testAlgorithmTimeComplexity(SortingClass, arrayToBeSorted, numberOfVisits) { diff --git a/src/algorithms/sorting/shell-sort/README.md b/src/algorithms/sorting/shell-sort/README.md new file mode 100644 index 00000000..9d202c55 --- /dev/null +++ b/src/algorithms/sorting/shell-sort/README.md @@ -0,0 +1,48 @@ +# Shellsort + +Shellsort, also known as Shell sort or Shell's method, +is an in-place comparison sort. It can be seen as either a +generalization of sorting by exchange (bubble sort) or sorting +by insertion (insertion sort). The method starts by sorting +pairs of elements far apart from each other, then progressively +reducing the gap between elements to be compared. Starting +with far apart elements, it can move some out-of-place +elements into position faster than a simple nearest neighbor +exchange + +![Shellsort](https://upload.wikimedia.org/wikipedia/commons/d/d8/Sorting_shellsort_anim.gif) + +## How Shell Sort Works + +For our example and ease of understanding, we take the interval +of `4`. Make a virtual sub-list of all values located at the +interval of 4 positions. Here these values are +`{35, 14}`, `{33, 19}`, `{42, 27}` and `{10, 44}` + +![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_gap_4.jpg) + +We compare values in each sub-list and swap them (if necessary) +in the original array. After this step, the new array should +look like this + +![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_step_1.jpg) + +Then, we take interval of 2 and this gap generates two sub-lists +- `{14, 27, 35, 42}`, `{19, 10, 33, 44}` + +![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_gap_2.jpg) + +We compare and swap the values, if required, in the original array. +After this step, the array should look like this + +![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_step_2.jpg) + +Finally, we sort the rest of the array using interval of value 1. +Shell sort uses insertion sort to sort the array. + +![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort.jpg) + +## References + +* [Tutorials Point](https://www.tutorialspoint.com/data_structures_algorithms/shell_sort_algorithm.htm) +* [Wikipedia](https://en.wikipedia.org/wiki/Shellsort) diff --git a/src/algorithms/sorting/shell-sort/ShellSort.js b/src/algorithms/sorting/shell-sort/ShellSort.js new file mode 100644 index 00000000..eac41045 --- /dev/null +++ b/src/algorithms/sorting/shell-sort/ShellSort.js @@ -0,0 +1,41 @@ +import Sort from '../Sort'; + +export default class ShellSort extends Sort { + sort(originalArray) { + // Prevent original array from mutations. + const array = originalArray.slice(0); + + // Define a gap distance. + let gap = Math.floor(array.length / 2); + + // Until gap is bigger then zero do elements comparisons and swaps. + while (gap > 0) { + // Go and compare all distant element pairs. + for (let i = 0; i < (array.length - gap); i += 1) { + let currentIndex = i; + let gapShiftedIndex = i + gap; + + while (currentIndex >= 0) { + // Call visiting callback. + this.callbacks.visitingCallback(array[currentIndex]); + + // Compare and swap array elements if needed. + if (this.comparator.lessThen(array[gapShiftedIndex], array[currentIndex])) { + const tmp = array[currentIndex]; + array[currentIndex] = array[gapShiftedIndex]; + array[gapShiftedIndex] = tmp; + } + + gapShiftedIndex = currentIndex; + currentIndex -= gap; + } + } + + // Shrink the gap. + gap = Math.floor(gap / 2); + } + + // Return sorted copy of an original array. + return array; + } +} diff --git a/src/algorithms/sorting/shell-sort/__test__/ShellSort.test.js b/src/algorithms/sorting/shell-sort/__test__/ShellSort.test.js new file mode 100644 index 00000000..c05aa6cd --- /dev/null +++ b/src/algorithms/sorting/shell-sort/__test__/ShellSort.test.js @@ -0,0 +1,56 @@ +import ShellSort from '../ShellSort'; +import { + equalArr, + notSortedArr, + reverseArr, + sortedArr, + SortTester, +} from '../../SortTester'; + +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 320; +const NOT_SORTED_ARRAY_VISITING_COUNT = 320; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 320; +const EQUAL_ARRAY_VISITING_COUNT = 320; + +describe('ShellSort', () => { + it('should sort array', () => { + SortTester.testSort(ShellSort); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(ShellSort); + }); + + it('should visit EQUAL array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + ShellSort, + equalArr, + EQUAL_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + ShellSort, + sortedArr, + SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit NOT SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + ShellSort, + notSortedArr, + NOT_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit REVERSE SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + ShellSort, + reverseArr, + REVERSE_SORTED_ARRAY_VISITING_COUNT, + ); + }); +});