Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(n<sup>2</sup>)**
- [Shell sort](src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java) **- O(n<sup>2</sup>) worst, ~O(n<sup>1.5</sup>) 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)**

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/williamfiset/algorithms/sorting/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)
61 changes: 61 additions & 0 deletions src/main/java/com/williamfiset/algorithms/sorting/ShellSort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Shell sort implementation — a generalization of insertion sort that allows the exchange of
* elements that are far apart.
*
* <p>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.
*
* <p>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.
*
* <p>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));
}
}
10 changes: 10 additions & 0 deletions src/test/java/com/williamfiset/algorithms/sorting/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -51,6 +52,7 @@ public InplaceSort getSortingAlgorithm() {
SortingAlgorithm.QUICK_SORT3,
SortingAlgorithm.RADIX_SORT,
SortingAlgorithm.SELECTION_SORT,
SortingAlgorithm.SHELL_SORT,
SortingAlgorithm.TIM_SORT);

@Test
Expand Down
Loading