-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f3ca5fd
commit 4e400bc
Showing
6 changed files
with
632 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
Sources/SortStateUniversity/Quicksort/Quicksort+Partition.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// | ||
// Quicksort+Partition.swift | ||
// Sort State University | ||
// | ||
// Created by Kyle Hughes on 6/22/24. | ||
// | ||
|
||
extension Quicksort { | ||
/// Represents a partition of the array being sorted by the ``Quicksort`` algorithm. | ||
/// | ||
/// A partition is a section of the array that is being sorted independently. It is defined by a lower bound, an | ||
/// upper bound, a current index, and a partition index. The ``Quicksort`` algorithm works by repeatedly | ||
/// partitioning the array and sorting these partitions. | ||
public struct Partition: Codable, Equatable, Hashable { | ||
/// The upper bound of the partition in the array being sorted. | ||
public let high: Elements.Index | ||
|
||
/// The lower bound of the partition in the array being sorted. | ||
public let low: Elements.Index | ||
|
||
/// The current index being examined in the partitioning process. | ||
/// | ||
/// This index moves from `low` towards `high`, comparing each element with the pivot element. | ||
public var currentIndex: Elements.Index | ||
|
||
/// The index that divides elements less than or equal to the pivot from those greater than the pivot. | ||
/// | ||
/// Elements to the left of this index (inclusive) are less than or equal to the pivot, while elements to the | ||
/// right are greater than the pivot. | ||
public var partitionIndex: Elements.Index | ||
|
||
/// Creates a new partition with the specified lower and upper bounds. | ||
/// | ||
/// Initially, both `currentIndex` and `partitionIndex` are set to `low`. | ||
/// | ||
/// - Parameter low: The lower bound of the partition. | ||
/// - Parameter high: The upper bound of the partition. | ||
@inlinable | ||
public init(low: Elements.Index, high: Elements.Index) { | ||
self.low = low | ||
self.high = high | ||
|
||
currentIndex = low | ||
partitionIndex = low | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
// | ||
// Quicksort.swift | ||
// SortStateUniversity | ||
// | ||
// Created by Kyle Hughes on 6/22/24. | ||
// | ||
|
||
import Foundation | ||
|
||
/// A divide-and-conquer sorting algorithm that uses a pivot element for partitioning the input array. | ||
/// | ||
/// - SeeAlso: https://en.wikipedia.org/wiki/Quicksort | ||
public struct Quicksort<Element>: Identifiable { | ||
/// A type that represents the collection of the elements that the algorithm is sorting. | ||
public typealias Elements = [Element] | ||
|
||
/// The stable identity of the algorithm. | ||
public let id: UUID | ||
|
||
/// The given elements that the algorithm is sorting. | ||
/// | ||
/// This value is constant and will not change after instantiation. | ||
public let input: Elements | ||
|
||
/// The current partition being sorted. | ||
public private(set) var currentPartition: Partition? | ||
|
||
/// The current result of applying the sorting algorithm to `input`. | ||
/// | ||
/// This value is "live" and will change as the algorithm is executed. When the algorithm is finished this value | ||
/// will contain the sorted result of `input`. It is primarily exposed to allow the internals of the algorithm to | ||
/// be observed. | ||
public private(set) var output: Elements | ||
|
||
/// A stack of partitions to be sorted. | ||
public private(set) var partitionStack: [Partition] | ||
|
||
// MARK: Public Initialization | ||
|
||
/// Creates an algorithm to sort the given input using quicksort. | ||
/// | ||
/// - Parameter input: The elements to sort. | ||
public init(input: Elements) { | ||
self.input = input | ||
|
||
currentPartition = nil | ||
id = UUID() | ||
output = input | ||
partitionStack = [Partition(low: input.startIndex, high: input.index(before: input.endIndex))] | ||
} | ||
} | ||
|
||
// MARK: - SortingAlgorithm Extension | ||
|
||
extension Quicksort: SortingAlgorithm { | ||
// MARK: Public Static Interface | ||
|
||
@inlinable | ||
public static var complexity: Complexity { | ||
.linearithmic | ||
} | ||
|
||
@inlinable | ||
public static var label: SortingAlgorithmLabel { | ||
.quicksort | ||
} | ||
|
||
/// Returns the average number of comparisons that the algorithm will perform given an input with `n` elements. | ||
/// | ||
/// This calculation uses the exact recurrence relation for Quicksort's average case. | ||
/// | ||
/// - SeeAlso: Sedgewick, R., & Flajolet, P. (1996). An Introduction to the Analysis of Algorithms. | ||
/// Addison-Wesley. Section 8.2: Quicksort. | ||
/// - Parameter n: The number of elements. | ||
/// - Returns: The average number of comparisons that the algorithm will perform. | ||
@inlinable | ||
public static func averageNumberOfComparisons(for n: Int) -> Int { | ||
guard 1 < n else { | ||
return 0 | ||
} | ||
|
||
var dynamicProgrammingTable = Array(repeating: 0, count: n + 1) | ||
|
||
for i in 2...n { | ||
dynamicProgrammingTable[i] = i - 1 + 2 * (0..<i).reduce(0) { $0 + dynamicProgrammingTable[$1] } / i | ||
} | ||
|
||
return dynamicProgrammingTable[n] | ||
} | ||
|
||
/// Returns the maximum number of comparisons that the algorithm will perform given an input with `n` elements. | ||
/// | ||
/// The worst case for quicksort occurs when the pivot is always the smallest or largest element. | ||
/// | ||
/// - SeeAlso: Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms | ||
/// (3rd ed.). MIT Press. Section 7.4.1: The worst-case partitioning. | ||
/// - Parameter n: The number of elements. | ||
/// - Returns: The maximum number of comparisons that the algorithm will perform. | ||
@inlinable | ||
public static func maximumNumberOfComparisons(for n: Int) -> Int { | ||
guard 1 < n else { | ||
return 0 | ||
} | ||
|
||
return (n * (n - 1)) / 2 | ||
} | ||
|
||
/// Returns the minimum number of comparisons that the algorithm will perform given an input with `n` elements. | ||
/// | ||
/// The best case for quicksort occurs when the pivot always divides the array into two equal halves. | ||
/// | ||
/// - SeeAlso: Knuth, D. E. (1998). The Art of Computer Programming, Volume 3: Sorting and Searching (2nd ed.). | ||
/// Addison-Wesley Professional. Section 5.2.2: Sorting by exchanging. | ||
/// - Parameter n: The number of elements. | ||
/// - Returns: The minimum number of comparisons that the algorithm will perform. | ||
@inlinable | ||
public static func minimumNumberOfComparisons(for n: Int) -> Int { | ||
guard 1 < n else { | ||
return 0 | ||
} | ||
|
||
var result = 0 | ||
var size = n | ||
|
||
while 1 < size { | ||
result += size - 1 | ||
size /= 2 | ||
} | ||
|
||
return result | ||
} | ||
|
||
// MARK: Public Instance Interface | ||
|
||
@inlinable | ||
public var isFinished: Bool { | ||
partitionStack.isEmpty && currentPartition == nil | ||
} | ||
|
||
public mutating func answer(_ answer: Comparison<Self>.Side) { | ||
guard var partition = currentPartition else { | ||
return | ||
} | ||
|
||
switch answer { | ||
case .left: | ||
output.swapAt(partition.currentIndex, partition.partitionIndex) | ||
partition.partitionIndex += 1 | ||
partition.currentIndex += 1 | ||
case .right: | ||
partition.currentIndex += 1 | ||
} | ||
|
||
currentPartition = partition | ||
} | ||
|
||
public mutating func callAsFunction() -> SortingAlgorithmStep<Self> { | ||
guard !isFinished else { | ||
return .finished(output) | ||
} | ||
|
||
if currentPartition == nil { | ||
guard let nextPartition = partitionStack.popLast() else { | ||
return .finished(output) | ||
} | ||
|
||
currentPartition = nextPartition | ||
} | ||
|
||
guard let partition = currentPartition else { | ||
return .finished(output) | ||
} | ||
|
||
guard partition.currentIndex >= partition.high else { | ||
return .comparison(Comparison(source: self)) | ||
} | ||
|
||
output.swapAt(partition.partitionIndex, partition.high) | ||
|
||
let pivotIndex = partition.partitionIndex | ||
|
||
if partition.low < pivotIndex { | ||
partitionStack.append(Partition(low: partition.low, high: pivotIndex - 1)) | ||
} | ||
|
||
if pivotIndex + 1 < partition.high { | ||
partitionStack.append(Partition(low: pivotIndex + 1, high: partition.high)) | ||
} | ||
|
||
currentPartition = nil | ||
|
||
return self() | ||
} | ||
|
||
@inlinable | ||
public func peekAtElement(for answer: Comparison<Self>.Side) -> Element? { | ||
guard let partition = currentPartition else { | ||
return nil | ||
} | ||
|
||
switch answer { | ||
case .left: | ||
return output[partition.currentIndex] | ||
case .right: | ||
return output[partition.high] | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Decodable Extension | ||
|
||
extension Quicksort: Decodable where Element: Decodable { | ||
// NO-OP | ||
} | ||
|
||
// MARK: - Encodable Extension | ||
|
||
extension Quicksort: Encodable where Element: Encodable { | ||
// NO-OP | ||
} | ||
|
||
// MARK: - Equatable Extension | ||
|
||
extension Quicksort: Equatable where Element: Equatable { | ||
// NO-OP | ||
} | ||
|
||
// MARK: - Hashable Extension | ||
|
||
extension Quicksort: Hashable where Element: Hashable { | ||
// NO-OP | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.