A Shell Sort Implementation in Ruby
This algorithm expands on concepts derived from Insertion Sort, and effectively uses those strategies by optimizing and consolidating swap operations per scan. Wikipedia offers some great background information for Shell Sort.
The example below is coded to the "poor" implementation of Shell Sort which operates in O(N2) time1, and room for improvements exist by optimizing the gap sequence; N/2 is the gap strategy used here. An effective gap strategy can lend a O(N3/2) down to O(N4/3) runtime.
def shell_sort (list) gap = list.length/2 while(gap > 0) list[gap..list.length-1].each_with_index do |element, index| j = (index += gap) index.step(gap-1, -gap) do |j| if ( j > 0 && j >= gap && element < list[j-gap] ) then list[j] = list[j-gap] else break end end list[j] = element end gap /= 2 end list end
Elements that are a "gap" apart, are compared and sorted; as the gap diminishes over each repitition, elements which are closest together are eventually compared.
| Shell Sort Example 1 | |
|---|---|
| Gap | Array Elements |
| input | 81 94 11 96 12 35 17 95 28 58 41 75 15 |
| 5 | 35 17 11 28 12 41 75 15 96 58 81 94 95 |
| 3 | 28 12 11 35 15 41 58 17 94 75 81 96 95 |
| 1 | 11 12 15 17 28 35 41 58 75 81 94 95 96 |
Code Review
Start with a gap of N/2, and iterate until adjacent elements have been processed
def shell_sort (list) gap = list.length/2 while(gap > 0)
Currently Ruby does not support an iterator which starts at a given index, so we will use the standard array API to obtain a sub-list which starts from "gap" and ends at the last element. We then iterate over that sub-list, and obtain each element and index.
list[gap..list.length-1].each_with_index do |element, index|
The sublist is returned as a seperate object, and therefore it's index starts at "0"; we must compensate for this by adjusting "index" to "index + gap". Additionally, we introduce "j" here so that we can use it outside of the upcoming loop; note, it's value at this point in the sequence is arbitrary and has no meaning.
j = (index += gap)
We then take advantage of Ruby's "step" construct for integers, to loop using a custom step sequence of "-gap", starting at "gap-1". As we scan down the list, we essentially compare and swap elements, in a similar fashion to Insertion Sort.
index.step(gap-1, -gap) do |j| if ( j > 0 && j >= gap && element < list[j-gap] ) then list[j] = list[j-gap] else break end end list[j] = element
I offer the same caveat as with the Insertion Sort tutorial; I attempted rubify the code, and therefore this implementation may not be as efficient as possible (e.g. use of "step" versus other loop tactics, the use of Ruby's sub-array functionality, having to re-adjust the index, etc..). However, I hope this provides some valuable insight into both Shell Sort and Ruby.
1. Weiss, Mark Allen (1999). "Data Structures and Algorithm Analysis in Java". Addison Wesley. ISBN 0201357542.
2. "Shell Sort". Wikipedia. http://en.wikipedia.org/wiki/Shell_sort