module Algorithms::Sort

This module implements sorting algorithms. Documentation is provided for each algorithm.

Public Class Methods

bubble_sort(container) click to toggle source

Bubble sort: A very naive sort that keeps swapping elements until the container is sorted. Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should be implemented for the container. Time Complexity: О(n^2) Space Complexity: О(n) total, O(1) auxiliary Stable: Yes

Algorithms::Sort.bubble_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
   # File lib/algorithms/sort.rb
16 def self.bubble_sort(container)
17   loop do
18     swapped = false
19     (container.size-1).times do |i|
20       if (container[i] <=> container[i+1]) == 1
21         container[i], container[i+1] = container[i+1], container[i] # Swap
22         swapped = true
23       end
24     end
25     break unless swapped
26   end
27   container
28 end
comb_sort(container) click to toggle source

Comb sort: A variation on bubble sort that dramatically improves performance. Source: yagni.com/combsort/ Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should be implemented for the container. Time Complexity: О(n^2) Space Complexity: О(n) total, O(1) auxiliary Stable: Yes

Algorithms::Sort.comb_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
   # File lib/algorithms/sort.rb
39 def self.comb_sort(container)
40   container
41   gap = container.size
42   loop do
43     gap = gap * 10/13
44     gap = 11 if gap == 9 || gap == 10
45     gap = 1 if gap < 1
46     swapped = false
47     (container.size - gap).times do |i|
48       if (container[i] <=> container[i + gap]) == 1
49         container[i], container[i+gap] = container[i+gap], container[i] # Swap
50         swapped = true
51       end
52     end
53     break if !swapped && gap == 1
54   end
55   container
56 end
dualpivot(container, left=0, right=container.size-1, div=3) click to toggle source
    # File lib/algorithms/sort.rb
278 def self.dualpivot(container, left=0, right=container.size-1, div=3)
279   length = right - left
280   if length < 27 # insertion sort for tiny array
281     container.each_with_index do |data,i|
282       j = i - 1
283       while j >= 0
284         break if container[j] <= data
285         container[j + 1] = container[j]
286         j = j - 1
287       end
288       container[j + 1] = data
289     end
290   else # full dual-pivot quicksort
291     third = length / div
292     # medians
293     m1 = left + third
294     m2 = right - third
295     if m1 <= left 
296       m1 = left + 1
297     end
298     if m2 >= right
299       m2 = right - 1
300     end
301     if container[m1] < container[m2]
302       dualpivot_swap(container, m1, left)
303       dualpivot_swap(container, m2, right)
304     else
305       dualpivot_swap(container, m1, right)
306       dualpivot_swap(container, m2, left)
307     end
308     # pivots
309     pivot1 = container[left]
310     pivot2 = container[right]
311     # pointers
312     less = left + 1
313     great = right - 1
314     # sorting
315     k = less
316     while k <= great
317       if container[k] < pivot1
318         dualpivot_swap(container, k, less += 1)
319       elsif container[k] > pivot2
320         while k < great && container[great] > pivot2
321           great -= 1
322         end
323         dualpivot_swap(container, k, great -= 1)
324         if container[k] < pivot1
325           dualpivot_swap(container, k, less += 1)
326         end
327       end
328       k += 1
329     end
330     # swaps
331     dist = great - less
332     if dist < 13
333       div += 1
334     end
335     dualpivot_swap(container, less-1, left)
336     dualpivot_swap(container, great+1, right)
337     # subarrays
338     dualpivot(container, left, less-2, div)
339     dualpivot(container, great+2, right, div)
340     # equal elements
341     if dist > length - 13 && pivot1 != pivot2
342       for k in less..great do
343         if container[k] == pivot1
344           dualpivot_swap(container, k, less)
345           less += 1
346         elsif container[k] == pivot2
347           dualpivot_swap(container, k, great)
348           great -= 1
349           if container[k] == pivot1
350             dualpivot_swap(container, k, less)
351             less += 1
352           end
353         end
354       end
355     end
356     # subarray
357     if pivot1 < pivot2
358       dualpivot(container, less, great, div)
359     end
360     container
361   end
362 end
dualpivot_swap(container, i, j) click to toggle source
    # File lib/algorithms/sort.rb
364 def self.dualpivot_swap(container, i, j)
365   container[i],  container[j] = container[j],  container[i]
366 end
dualpivotquicksort(container) click to toggle source

Dual-Pivot Quicksort is a variation of Quicksort by Vladimir Yaroslavskiy. This is an implementation of the algorithm as it was found in the original research paper:

iaroslavski.narod.ru/quicksort/DualPivotQuicksort.pdf

Mirror: codeblab.com/wp-content/uploads/2009/09/DualPivotQuicksort.pdf

“This algorithm offers O(n log(n)) performance on many data sets that cause other quicksorts to degrade to quadratic performance, and is typically faster than traditional (one-pivot) Quicksort implementations.”

-- http://download.oracle.com/javase/7/docs/api/java/util/Arrays.html

The algorithm was improved by Vladimir Yaroslavskiy, Jon Bentley, and Joshua Bloch, and was implemented as the default sort algorithm for primatives in Java 7.

Implementation in the Java JDK as of November, 2011: www.docjar.com/html/api/java/util/DualPivotQuicksort.java.html

It is proved that for the Dual-Pivot Quicksort the average number of comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n), whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n) respectively. This has been fully examined mathematically and experimentally.

Requirements: Container should implement pop and include the Enumerable module. Time Complexity: О(n log n) average, О(n log n) worst-case Space Complexity: О(n) auxiliary

Stable: No

Algorithms::Sort.dualpivotquicksort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
    # File lib/algorithms/sort.rb
273 def self.dualpivotquicksort(container)
274   return container if container.size <= 1
275   dualpivot(container, 0, container.size-1, 3)
276 end
heapsort(container) click to toggle source

Heap sort: Uses a heap (implemented by the Containers module) to sort the collection. Requirements: Needs to be able to compare elements with <=> Time Complexity: О(n^2) Space Complexity: О(n) total, O(1) auxiliary Stable: Yes

Algorithms::Sort.heapsort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
   # File lib/algorithms/sort.rb
85 def self.heapsort(container)
86   heap = Containers::Heap.new(container)
87   ary = []
88   ary << heap.pop until heap.empty?
89   ary
90 end
insertion_sort(container) click to toggle source

Insertion sort: Elements are inserted sequentially into the right position. Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should be implemented for the container. Time Complexity: О(n^2) Space Complexity: О(n) total, O(1) auxiliary Stable: Yes

Algorithms::Sort.insertion_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
    # File lib/algorithms/sort.rb
100 def self.insertion_sort(container)
101   return container if container.size < 2
102   (1..container.size-1).each do |i|
103     value = container[i]
104     j = i-1
105     while j >= 0 and container[j] > value do
106       container[j+1] = container[j]
107       j = j-1
108     end
109     container[j+1] = value
110   end
111   container
112 end
merge(left, right) click to toggle source
    # File lib/algorithms/sort.rb
230 def self.merge(left, right)
231   sorted = []
232   until left.empty? or right.empty?
233     left.first <= right.first ? sorted << left.shift : sorted << right.shift
234   end
235   sorted + left + right
236 end
mergesort(container) click to toggle source

Mergesort: A stable divide-and-conquer sort that sorts small chunks of the container and then merges them together. Returns an array of the sorted elements. Requirements: Container should implement [] Time Complexity: О(n log n) average and worst-case Space Complexity: О(n) auxiliary Stable: Yes

Algorithms::Sort.mergesort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
    # File lib/algorithms/sort.rb
222 def self.mergesort(container)
223   return container if container.size <= 1
224   mid   = container.size / 2
225   left  = container[0...mid]
226   right = container[mid...container.size]
227   merge(mergesort(left), mergesort(right))
228 end
partition(data, left, right) click to toggle source

Quicksort: A divide-and-conquer sort that recursively partitions a container until it is sorted. Requirements: Container should implement pop and include the Enumerable module. Time Complexity: О(n log n) average, O(n^2) worst-case Space Complexity: О(n) auxiliary Stable: No

Algorithms::Sort.quicksort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]

def self.quicksort(container)

return [] if container.empty?

x, *xs = container

quicksort(xs.select { |i| i <  x }) + [x] + quicksort(xs.select { |i| i >= x })

end

    # File lib/algorithms/sort.rb
154 def self.partition(data, left, right)
155   pivot = data[front]
156   left += 1
157 
158   while left <= right do
159     if data[frontUnknown] < pivot
160       back += 1
161       data[frontUnknown], data[back] = data[back], data[frontUnknown] # Swap
162     end
163 
164     frontUnknown += 1
165   end
166 
167   data[front], data[back] = data[back], data[front] # Swap
168   back
169 end
quicksort(container) click to toggle source

def self.quicksort(container, left = 0, right = container.size - 1)

if left < right 
  middle = partition(container, left, right)
  quicksort(container, left, middle - 1)
  quicksort(container, middle + 1, right)
end

end

    # File lib/algorithms/sort.rb
180 def self.quicksort(container)
181   bottom, top = [], []
182   top[0] = 0
183   bottom[0] = container.size
184   i = 0
185   while i >= 0 do
186     l = top[i]
187     r = bottom[i] - 1;
188     if l < r
189       pivot = container[l]
190       while l < r do
191         r -= 1 while (container[r] >= pivot  && l < r)
192         if (l < r)
193           container[l] = container[r]
194           l += 1
195         end
196         l += 1 while (container[l] <= pivot  && l < r)
197         if (l < r)
198           container[r] = container[l]
199           r -= 1
200         end
201       end
202       container[l] = pivot
203       top[i+1] = l + 1
204       bottom[i+1] = bottom[i]
205       bottom[i] = l
206       i += 1
207     else
208       i -= 1
209     end
210   end
211   container    
212 end
selection_sort(container) click to toggle source

Selection sort: A naive sort that goes through the container and selects the smallest element, putting it at the beginning. Repeat until the end is reached. Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should be implemented for the container. Time Complexity: О(n^2) Space Complexity: О(n) total, O(1) auxiliary Stable: Yes

Algorithms::Sort.selection_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
   # File lib/algorithms/sort.rb
67 def self.selection_sort(container)
68   0.upto(container.size-1) do |i|
69     min = i
70     (i+1).upto(container.size-1) do |j|
71       min = j if (container[j] <=> container[min]) == -1
72     end
73     container[i], container[min] = container[min], container[i] # Swap
74   end
75   container
76 end
shell_sort(container) click to toggle source

Shell sort: Similar approach as insertion sort but slightly better. Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should be implemented for the container. Time Complexity: О(n^2) Space Complexity: О(n) total, O(1) auxiliary Stable: Yes

Algorithms::Sort.shell_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
    # File lib/algorithms/sort.rb
122 def self.shell_sort(container)
123   increment = container.size/2
124   while increment > 0 do
125     (increment..container.size-1).each do |i|
126       temp = container[i]
127       j = i
128       while j >= increment && container[j - increment] > temp do
129         container[j] = container[j-increment]
130         j -= increment
131       end
132       container[j] = temp
133     end
134     increment = (increment == 2 ? 1 : (increment / 2.2).round)
135   end
136   container
137 end