From a0ddfc8029af8c849da5598dc9d3b87c1e18b5cb Mon Sep 17 00:00:00 2001 From: William Date: Mon, 22 Jun 2026 20:42:44 -0700 Subject: [PATCH] Add Shell sort implementation with tests Implement Shell sort as a gapped insertion sort using the original Shell gap sequence (n/2, n/4, ..., 1), following the repo's sorting conventions: - ShellSort implements the shared InplaceSort interface, with a teaching doc comment (how/why, time/space complexity) and a runnable main method. - ShellSortTest mirrors the existing sort tests (edge cases + randomized). - Register SHELL_SORT in the shared SortingTest enum/EnumSet so it is covered by the cross-algorithm property tests. - Add Bazel java_binary / java_test targets and a README entry. Co-Authored-By: Claude Opus 4.8 Co-authored-by: Sculptor --- README.md | 1 + .../com/williamfiset/algorithms/sorting/BUILD | 7 ++ .../algorithms/sorting/ShellSort.java | 61 +++++++++++++ .../com/williamfiset/algorithms/sorting/BUILD | 10 +++ .../algorithms/sorting/ShellSortTest.java | 86 +++++++++++++++++++ .../algorithms/sorting/SortingTest.java | 2 + 6 files changed, 167 insertions(+) create mode 100644 src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java create mode 100644 src/test/java/com/williamfiset/algorithms/sorting/ShellSortTest.java diff --git a/README.md b/README.md index 72679b39a..3a38fb59a 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Quicksort (in-place, Hoare partitioning)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java) **- Θ(nlog(n))** - [Quicksort3 (Dutch National Flag algorithm)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java) **- Θ(nlog(n))** - [Selection sort](src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java) **- O(n2)** +- [Shell sort](src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java) **- O(n2) worst, ~O(n1.5) average** - [Tim sort](src/main/java/com/williamfiset/algorithms/sorting/TimSort.java) **- O(nlog(n))** - [Radix sort](src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java) **- O(n\*w)** diff --git a/src/main/java/com/williamfiset/algorithms/sorting/BUILD b/src/main/java/com/williamfiset/algorithms/sorting/BUILD index 91238898b..666f45af4 100644 --- a/src/main/java/com/williamfiset/algorithms/sorting/BUILD +++ b/src/main/java/com/williamfiset/algorithms/sorting/BUILD @@ -90,3 +90,10 @@ java_binary( main_class = "com.williamfiset.algorithms.sorting.SelectionSort", runtime_deps = [":sorting"], ) + +# bazel run //src/main/java/com/williamfiset/algorithms/sorting:ShellSort +java_binary( + name = "ShellSort", + main_class = "com.williamfiset.algorithms.sorting.ShellSort", + runtime_deps = [":sorting"], +) diff --git a/src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java b/src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java new file mode 100644 index 000000000..1362afd3e --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java @@ -0,0 +1,61 @@ +/** + * Shell sort implementation — a generalization of insertion sort that allows the exchange of + * elements that are far apart. + * + *

Plain insertion sort only ever compares and swaps neighbouring elements, so an element that + * belongs near the front but starts near the back has to crawl there one step at a time. Shell sort + * speeds this up by first sorting elements that are a fixed distance ("gap") apart, then repeatedly + * shrinking the gap until it reaches 1. The large-gap passes move badly-placed elements most of the + * way to their final position in big jumps, so by the time we run the final gap-1 pass (an ordinary + * insertion sort) the array is "almost sorted" and that cheap pass has very little work left to do. + * + *

This implementation uses the original Shell gap sequence (n/2, n/4, ..., 1). Smarter gap + * sequences (e.g. Hibbard, Sedgewick) give better worst-case bounds. + * + *

Time Complexity: O(n^2) worst case with the Shell sequence, roughly O(n^1.5) on average, and + * O(n log n) best case (already sorted). Space Complexity: O(1) — sorts in place. + * + * @author Sculptor + */ +package com.williamfiset.algorithms.sorting; + +public class ShellSort implements InplaceSort { + + @Override + public void sort(int[] values) { + ShellSort.shellSort(values); + } + + // Sort the given array in place using shell sort. For each gap in the + // decreasing sequence n/2, n/4, ..., 1 we run a gapped insertion sort: + // each element is shifted backwards in steps of `gap` until it sits in the + // correct position relative to the other elements of its gapped subsequence. + private static void shellSort(int[] ar) { + if (ar == null) { + return; + } + + int n = ar.length; + for (int gap = n / 2; gap > 0; gap /= 2) { + // Gapped insertion sort: ar[gap..n) is inserted into the already + // gap-sorted elements that precede it. + for (int i = gap; i < n; i++) { + int tmp = ar[i]; + int j = i; + for (; j >= gap && ar[j - gap] > tmp; j -= gap) { + ar[j] = ar[j - gap]; + } + ar[j] = tmp; + } + } + } + + public static void main(String[] args) { + InplaceSort sorter = new ShellSort(); + int[] array = {10, 4, 6, 4, 8, -13, 2, 3}; + sorter.sort(array); + // Prints: + // [-13, 2, 3, 4, 4, 6, 8, 10] + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/sorting/BUILD b/src/test/java/com/williamfiset/algorithms/sorting/BUILD index 092e69fe2..7678484d0 100644 --- a/src/test/java/com/williamfiset/algorithms/sorting/BUILD +++ b/src/test/java/com/williamfiset/algorithms/sorting/BUILD @@ -137,6 +137,16 @@ java_test( deps = TEST_DEPS, ) +java_test( + name = "ShellSortTest", + srcs = ["ShellSortTest.java"], + main_class = "org.junit.platform.console.ConsoleLauncher", + use_testrunner = False, + args = ["--select-class=com.williamfiset.algorithms.sorting.ShellSortTest"], + runtime_deps = JUNIT5_RUNTIME_DEPS, + deps = TEST_DEPS, +) + java_test( name = "SortingTest", srcs = ["SortingTest.java"], diff --git a/src/test/java/com/williamfiset/algorithms/sorting/ShellSortTest.java b/src/test/java/com/williamfiset/algorithms/sorting/ShellSortTest.java new file mode 100644 index 000000000..0c15ebfd3 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/sorting/ShellSortTest.java @@ -0,0 +1,86 @@ +package com.williamfiset.algorithms.sorting; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import java.util.Arrays; +import org.junit.jupiter.api.*; + +public class ShellSortTest { + + private final ShellSort sorter = new ShellSort(); + + @Test + public void testEmptyArray() { + int[] array = {}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {}); + } + + @Test + public void testSingleElement() { + int[] array = {42}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {42}); + } + + @Test + public void testAlreadySorted() { + int[] array = {1, 2, 3, 4, 5}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5}); + } + + @Test + public void testReverseSorted() { + int[] array = {5, 4, 3, 2, 1}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5}); + } + + @Test + public void testWithDuplicates() { + int[] array = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 1, 2, 3, 3, 4, 5, 5, 6, 9}); + } + + @Test + public void testAllSameElements() { + int[] array = {4, 4, 4, 4}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {4, 4, 4, 4}); + } + + @Test + public void testNegativeNumbers() { + int[] array = {-3, -1, -4, -1, -5}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {-5, -4, -3, -1, -1}); + } + + @Test + public void testMixedPositiveAndNegative() { + int[] array = {3, -2, 0, 7, -5, 1}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {-5, -2, 0, 1, 3, 7}); + } + + @Test + public void testTwoElements() { + int[] array = {9, 1}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 9}); + } + + @Test + public void testRandomized() { + for (int size = 0; size < 500; size++) { + int[] values = TestUtils.randomIntegerArray(size, -50, 51); + int[] expected = values.clone(); + Arrays.sort(expected); + sorter.sort(values); + assertThat(values).isEqualTo(expected); + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java index 3ff9b497f..f41c06258 100644 --- a/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java +++ b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java @@ -26,6 +26,7 @@ enum SortingAlgorithm { QUICK_SORT3(new QuickSort3()), RADIX_SORT(new RadixSort()), SELECTION_SORT(new SelectionSort()), + SHELL_SORT(new ShellSort()), TIM_SORT(new TimSort()); private InplaceSort algorithm; @@ -51,6 +52,7 @@ public InplaceSort getSortingAlgorithm() { SortingAlgorithm.QUICK_SORT3, SortingAlgorithm.RADIX_SORT, SortingAlgorithm.SELECTION_SORT, + SortingAlgorithm.SHELL_SORT, SortingAlgorithm.TIM_SORT); @Test