Shell sort
Shell sort is an extension of insertion sort because it permits to exchange elements of the array that are far apart. It does it sorting elements at specific gaps which produces partially sorted slices that can be eventually sorted by insertion sort.
It works as follows:
- Choose a sequence of gaps: This is the most difficult part of the Shellsort algorithm. Any sequence that contains 1 yields a correct sort because it makes the final pass an ordinary insertion sort.
- Sort the gap sub arrays: Each element in the gap sequence generates a sub array of elements spaced by that gap value. This step sorts the sub array using insertion sort generating a partially sorted general array.
- Repeat for smaller gaps: Continue the process with the rest of the elements of the gap sequence until the final pass which is a regular insertion sort with a gap of 1. At that stage the array is close to being sorted which is where the insertion sort is more efficient.
There has been proposed different gap sequences such as:
| OEIS | General term | Gap sequence | Time complexity | Author and date |
|---|---|---|---|---|
| , | Donald Shell in 1959 | |||
| A003462 | 1 | Donald Knuth in 1973 | ||
| A036562 | Sedgewick in 1982 | |||
| A102549 | Unknown2 | Unknown | Marcin Ciura in 2001 |
1
not greater than
2
experimentally derived
The implementation admits passing one of the previous sequences:
/// Sorts the slice using shell sort algorithm and the comparison function `is_less` passed as
/// argument. The decreasing interval sequence is also passed as argument.
fn shell_sort<T, F, S>(v: &mut [T], mut is_less: F, seq: S)
where
F: FnMut(&T, &T) -> bool,
S: Iterator<Item = usize>,
{
for h in seq {
let mut i = h;
while i < v.len() {
let mut j = i;
while j >= h && is_less(&v[j], &v[j - h]) {
v.swap(j, j - h);
j -= h;
}
i += 1;
}
}
}
The average algorithm performance depends on the gap sequence used. Using the bench cli tool on a M1 processor executing 100 iterations the results obtained are as follows:
| sequence | 1000 | 5000 | 10000 | 50000 | 100000 | 1000000 |
|---|---|---|---|---|---|---|
| shell.rs | 91.306µs | 471.897µs | 935.834µs | 5.626ms | 11.333ms | 159.381ms |
| knuth.rs | 79.847µs | 423.083µs | 813.259µs | 4.575ms | 10.115ms | 146.414ms |
| sedgewick.rs | 78.781µs | 395.065µs | 732.768µs | 3.996ms | 8.484ms | 106.259ms |
| ciura.rs | 84.742µs | 430.146µs | 811.698µs | 4.318ms | 9.784ms | 309.116ms |
See the algorithm in action using the Knuth sequence:
The full source code can be found here