|
| 1 | +//: Playground - noun: a place where people can play |
| 2 | + |
| 3 | +import Foundation |
| 4 | + |
| 5 | +func introsort<T>(_ array: inout [T], by areInIncreasingOrder: (T, T) -> Bool) { |
| 6 | + //The depth limit is as best practice 2 * log( n ) |
| 7 | + let depthLimit = 2 * floor(log2(Double(array.count))) |
| 8 | + |
| 9 | + introSortImplementation(for: &array, range: 0..<array.count, depthLimit: Int(depthLimit), by: areInIncreasingOrder) |
| 10 | +} |
| 11 | + |
| 12 | +///This method is recursively executed for each partition result of the quicksort part of the algorithm |
| 13 | +private func introSortImplementation<T>(for array: inout [T], range: Range<Int>, depthLimit: Int, by areInIncreasingOrder: (T, T) -> Bool) { |
| 14 | + if array.distance(from: range.lowerBound, to: range.upperBound) < 20 { |
| 15 | + //if the partition count is less than 20 we can sort it using insertion sort. This algorithm in fact performs well on collections |
| 16 | + //of this size, plus, at this point is quite probable that the quisksort part of the algorithm produced a partition which is |
| 17 | + //nearly sorted. As we knoe insertion sort tends to O( n ) if this is the case. |
| 18 | + insertionSort(for: &array, range: range, by: areInIncreasingOrder) |
| 19 | + } else if depthLimit == 0 { |
| 20 | + //If we reached the depth limit for this recursion branch, it's possible that we are hitting quick sort's worst case. |
| 21 | + //Since quicksort degrades to O( n^2 ) in its worst case we stop using quicksort for this recursion branch and we switch to heapsort. |
| 22 | + //Our preference remains quicksort, and we hope to be rare to see this condition to be true |
| 23 | + heapsort(for: &array, range: range, by: areInIncreasingOrder) |
| 24 | + } else { |
| 25 | + //By default we use quicksort to sort our collection. The partition index method chose a pivot, and puts all the |
| 26 | + //elements less than pivot on the left, and the ones bigger than pivot on the right. At the end of the operation the |
| 27 | + //position of the pivot in the array is returned so that we can form the two partitions. |
| 28 | + let partIdx = partitionIndex(for: &array, subRange: range, by: areInIncreasingOrder) |
| 29 | + |
| 30 | + //We can recursively call introsort implementation, decreasing the depthLimit for the left partition and the right partition. |
| 31 | + introSortImplementation(for: &array, range: range.lowerBound..<partIdx, depthLimit: depthLimit &- 1, by: areInIncreasingOrder) |
| 32 | + introSortImplementation(for: &array, range: partIdx..<range.upperBound, depthLimit: depthLimit &- 1, by: areInIncreasingOrder) |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +var unsorted = randomize(n: 1000) |
| 37 | + |
| 38 | +let swiftSorted = unsorted.sorted() |
| 39 | +introsort(&unsorted, by: <) |
| 40 | + |
| 41 | +print("does it work? \(unsorted == swiftSorted)") |
0 commit comments