Chapter 34: Quicksort
前言
Quicksort也是comparison-based sorting algorithm跟Merge sort很像,但有一個很重要特性Quicksort利用pivot來分割資料集
[ elements < pivot | pivot | elements > pivot ]
大綱
Example
Partitioning strategies
Lomuto’s partitioning
Hoare’s partitioning
Effects of a bad pivot choice
Median of three
Dutch national flag partitioning
Example
每次都選擇中間當pivot
public func quicksortNaive<T: Comparable>(_ a: [T]) -> [T] {
guard a.count > 1 else {
return a
}
let pivot = a[a.count / 2]
let less = a.filter { $0 < pivot }
let equal = a.filter { $0 == pivot }
let greater = a.filter { $0 > pivot }
return quicksortNaive(less) + equal + quicksortNaive(greater)
}

Calling filter three times on the same array is not efficient.
Creating a new array for every partition isn’t space efficient. Could you possibly sort in place?
Is picking the middle element the best pivot strategy? What pivot strategy should you adopt?
Lomuto’s partitioning
Always chooses the last element as the pivot
[ values <= pivot | values > pivot | not compared yet | pivot ]
low i-1 i j-1 j high-1 high
public func partitonLomuto<T: Comparable>(_ a: inout [T], low: Int, high: Int) -> Int {
// Lomuto是將最後一個元素設成pivot
let pivot = a[high]
// i用來記錄有多少元素小於pivot
var i = low
// 遍歷low到high, 但不包括high, 因為high已經拿來當pivot
for j in low..<high {
if a[j] <= pivot {
// 若j指向的元素小於pivot,則跟i當前指向元素進行交換
a.swapAt(i, j)
i += 1
}
}
// 最後跟pivot交換,讓pivot永遠是小於區塊跟大於區塊的分界點
a.swapAt(i, high)
return i
}
public func quicksortLomuto<T: Comparable>(_ a: inout [T], low: Int, high: Int) {
// 遞迴終止條件
if low < high {
let pivot = partitonLomuto(&a, low: low, high: high)
// 遞迴
quicksortLomuto(&a, low: low, high: pivot - 1)
quicksortLomuto(&a, low: pivot + 1, high: high)
}
}
Hoare’s partitioning
Always chooses the First element as the pivot
public func partitionHoare<T: Comparable>(_ a: inout [T], low: Int, high: Int) -> Int {
// Hoare是將第一個元素設成pivot
let pivot = a[low]
// 在i之前的元素,是小於等於pivot
var i = low - 1
// 在j之後的元素,是大於pivot
var j = high + 1
while true {
repeat { j -= 1 } while a[j] > pivot
repeat { i += 1 } while a[i] < pivot
if i < j {
// 當i所指元素大於pivot
// 或j所指元素小於pivot
a.swapAt(i, j)
} else {
// 迴圈終止條件
return j
}
}
}
public func quicksortHoare<T: Comparable>(_ a: inout [T], low: Int, high: Int) {
if low < high {
let p = partitionHoare(&a, low: low, high: high)
quicksortHoare(&a, low: low, high: p)
quicksortHoare(&a, low: p + 1, high: high)
}
}
Median’s partitioning
非最前,非最後,非中間
public func medianOfThree<T: Comparable>(_ a: inout [T], low: Int, high: Int) -> Int {
let center = (low + high) / 2
if a[low] > a[center] {
a.swapAt(low, center)
}
if a[low] > a[high] {
a.swapAt(low, high)
}
if a[center] > a[high] {
a.swapAt(center, high)
}
return center
}
public func quickSortMedian<T: Comparable>(_ a: inout [T], low: Int, high: Int) {
if low < high {
// 預處理,找一個非第一個,非最後一個的元素
let pivotIndex = medianOfThree(&a, low: low, high: high)
a.swapAt(pivotIndex, high)
let pivot = partitonLomuto(&a, low: low, high: high)
quicksortLomuto(&a, low: low, high: pivot - 1)
quicksortLomuto(&a, low: pivot + 1, high: high)
}
}
Dutch national flag partitioning
比前幾個partitioning多處理相同的元素,相同的元素會放在一起
public func partitionDutchFlag<T: Comparable>(_ a: inout [T], low: Int, high: Int, pivotIndex: Int) -> (Int, Int) {
let pivot = a[pivotIndex]
var smaller = low
// 在smaller和equal中間的所有元素皆等於pivot
var equal = low
var larger = high
// 遞迴終止條件
while equal <= larger {
if a[equal] < pivot {
// 當搬元素到小於的區域時,smaller,equal兩根指針都要向右移
a.swapAt(smaller, equal)
smaller += 1
equal += 1
} else if a[equal] == pivot {
// 當搬元素到等於的區域時,equal指針都要向右移
equal += 1
} else {
// 當搬元素到大於的區域時,larger指針都要向左移
a.swapAt(equal, larger)
larger -= 1
}
}
return (smaller, larger)
}
public func quicksortDutchFlag<T: Comparable>(_ a: inout [T], low: Int, high: Int) {
if low < high {
let (middleFirst, middleLast) = partitionDutchFlag(&a, low: low, high: high, pivotIndex: high)
quicksortDutchFlag(&a, low: low, high: middleFirst - 1)
quicksortDutchFlag(&a, low: middleLast + 1, high: high)
}
}
Last updated
Was this helpful?