Skip to content

Commit 08c6d5f

Browse files
williamfisetSculptor
andauthored
Add Shell sort implementation with tests (williamfiset#1319)
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: Sculptor <sculptor@imbue.com>
1 parent 4371d09 commit 08c6d5f

6 files changed

Lines changed: 167 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
283283
- [Quicksort (in-place, Hoare partitioning)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java) **- Θ(nlog(n))**
284284
- [Quicksort3 (Dutch National Flag algorithm)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java) **- Θ(nlog(n))**
285285
- [Selection sort](src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java) **- O(n<sup>2</sup>)**
286+
- [Shell sort](src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java) **- O(n<sup>2</sup>) worst, ~O(n<sup>1.5</sup>) average**
286287
- [Tim sort](src/main/java/com/williamfiset/algorithms/sorting/TimSort.java) **- O(nlog(n))**
287288
- [Radix sort](src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java) **- O(n\*w)**
288289

src/main/java/com/williamfiset/algorithms/sorting/BUILD

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,10 @@ java_binary(
9090
main_class = "com.williamfiset.algorithms.sorting.SelectionSort",
9191
runtime_deps = [":sorting"],
9292
)
93+
94+
# bazel run //src/main/java/com/williamfiset/algorithms/sorting:ShellSort
95+
java_binary(
96+
name = "ShellSort",
97+
main_class = "com.williamfiset.algorithms.sorting.ShellSort",
98+
runtime_deps = [":sorting"],
99+
)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Shell sort implementation — a generalization of insertion sort that allows the exchange of
3+
* elements that are far apart.
4+
*
5+
* <p>Plain insertion sort only ever compares and swaps neighbouring elements, so an element that
6+
* belongs near the front but starts near the back has to crawl there one step at a time. Shell sort
7+
* speeds this up by first sorting elements that are a fixed distance ("gap") apart, then repeatedly
8+
* shrinking the gap until it reaches 1. The large-gap passes move badly-placed elements most of the
9+
* way to their final position in big jumps, so by the time we run the final gap-1 pass (an ordinary
10+
* insertion sort) the array is "almost sorted" and that cheap pass has very little work left to do.
11+
*
12+
* <p>This implementation uses the original Shell gap sequence (n/2, n/4, ..., 1). Smarter gap
13+
* sequences (e.g. Hibbard, Sedgewick) give better worst-case bounds.
14+
*
15+
* <p>Time Complexity: O(n^2) worst case with the Shell sequence, roughly O(n^1.5) on average, and
16+
* O(n log n) best case (already sorted). Space Complexity: O(1) — sorts in place.
17+
*
18+
* @author Sculptor
19+
*/
20+
package com.williamfiset.algorithms.sorting;
21+
22+
public class ShellSort implements InplaceSort {
23+
24+
@Override
25+
public void sort(int[] values) {
26+
ShellSort.shellSort(values);
27+
}
28+
29+
// Sort the given array in place using shell sort. For each gap in the
30+
// decreasing sequence n/2, n/4, ..., 1 we run a gapped insertion sort:
31+
// each element is shifted backwards in steps of `gap` until it sits in the
32+
// correct position relative to the other elements of its gapped subsequence.
33+
private static void shellSort(int[] ar) {
34+
if (ar == null) {
35+
return;
36+
}
37+
38+
int n = ar.length;
39+
for (int gap = n / 2; gap > 0; gap /= 2) {
40+
// Gapped insertion sort: ar[gap..n) is inserted into the already
41+
// gap-sorted elements that precede it.
42+
for (int i = gap; i < n; i++) {
43+
int tmp = ar[i];
44+
int j = i;
45+
for (; j >= gap && ar[j - gap] > tmp; j -= gap) {
46+
ar[j] = ar[j - gap];
47+
}
48+
ar[j] = tmp;
49+
}
50+
}
51+
}
52+
53+
public static void main(String[] args) {
54+
InplaceSort sorter = new ShellSort();
55+
int[] array = {10, 4, 6, 4, 8, -13, 2, 3};
56+
sorter.sort(array);
57+
// Prints:
58+
// [-13, 2, 3, 4, 4, 6, 8, 10]
59+
System.out.println(java.util.Arrays.toString(array));
60+
}
61+
}

src/test/java/com/williamfiset/algorithms/sorting/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ java_test(
137137
deps = TEST_DEPS,
138138
)
139139

140+
java_test(
141+
name = "ShellSortTest",
142+
srcs = ["ShellSortTest.java"],
143+
main_class = "org.junit.platform.console.ConsoleLauncher",
144+
use_testrunner = False,
145+
args = ["--select-class=com.williamfiset.algorithms.sorting.ShellSortTest"],
146+
runtime_deps = JUNIT5_RUNTIME_DEPS,
147+
deps = TEST_DEPS,
148+
)
149+
140150
java_test(
141151
name = "SortingTest",
142152
srcs = ["SortingTest.java"],
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.williamfiset.algorithms.sorting;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import com.williamfiset.algorithms.utils.TestUtils;
6+
import java.util.Arrays;
7+
import org.junit.jupiter.api.*;
8+
9+
public class ShellSortTest {
10+
11+
private final ShellSort sorter = new ShellSort();
12+
13+
@Test
14+
public void testEmptyArray() {
15+
int[] array = {};
16+
sorter.sort(array);
17+
assertThat(array).isEqualTo(new int[] {});
18+
}
19+
20+
@Test
21+
public void testSingleElement() {
22+
int[] array = {42};
23+
sorter.sort(array);
24+
assertThat(array).isEqualTo(new int[] {42});
25+
}
26+
27+
@Test
28+
public void testAlreadySorted() {
29+
int[] array = {1, 2, 3, 4, 5};
30+
sorter.sort(array);
31+
assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5});
32+
}
33+
34+
@Test
35+
public void testReverseSorted() {
36+
int[] array = {5, 4, 3, 2, 1};
37+
sorter.sort(array);
38+
assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5});
39+
}
40+
41+
@Test
42+
public void testWithDuplicates() {
43+
int[] array = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
44+
sorter.sort(array);
45+
assertThat(array).isEqualTo(new int[] {1, 1, 2, 3, 3, 4, 5, 5, 6, 9});
46+
}
47+
48+
@Test
49+
public void testAllSameElements() {
50+
int[] array = {4, 4, 4, 4};
51+
sorter.sort(array);
52+
assertThat(array).isEqualTo(new int[] {4, 4, 4, 4});
53+
}
54+
55+
@Test
56+
public void testNegativeNumbers() {
57+
int[] array = {-3, -1, -4, -1, -5};
58+
sorter.sort(array);
59+
assertThat(array).isEqualTo(new int[] {-5, -4, -3, -1, -1});
60+
}
61+
62+
@Test
63+
public void testMixedPositiveAndNegative() {
64+
int[] array = {3, -2, 0, 7, -5, 1};
65+
sorter.sort(array);
66+
assertThat(array).isEqualTo(new int[] {-5, -2, 0, 1, 3, 7});
67+
}
68+
69+
@Test
70+
public void testTwoElements() {
71+
int[] array = {9, 1};
72+
sorter.sort(array);
73+
assertThat(array).isEqualTo(new int[] {1, 9});
74+
}
75+
76+
@Test
77+
public void testRandomized() {
78+
for (int size = 0; size < 500; size++) {
79+
int[] values = TestUtils.randomIntegerArray(size, -50, 51);
80+
int[] expected = values.clone();
81+
Arrays.sort(expected);
82+
sorter.sort(values);
83+
assertThat(values).isEqualTo(expected);
84+
}
85+
}
86+
}

src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ enum SortingAlgorithm {
2626
QUICK_SORT3(new QuickSort3()),
2727
RADIX_SORT(new RadixSort()),
2828
SELECTION_SORT(new SelectionSort()),
29+
SHELL_SORT(new ShellSort()),
2930
TIM_SORT(new TimSort());
3031

3132
private InplaceSort algorithm;
@@ -51,6 +52,7 @@ public InplaceSort getSortingAlgorithm() {
5152
SortingAlgorithm.QUICK_SORT3,
5253
SortingAlgorithm.RADIX_SORT,
5354
SortingAlgorithm.SELECTION_SORT,
55+
SortingAlgorithm.SHELL_SORT,
5456
SortingAlgorithm.TIM_SORT);
5557

5658
@Test

0 commit comments

Comments
 (0)