【问题标题】:Swift Quicksort Algorithm快速快速排序算法
【发布时间】:2021-07-29 09:19:59
【问题描述】:

一段时间以来一直在尝试用 Swift 编写以下快速排序算法,但无法解决问题。 [快速排序,因为数组中有大约 15,000 个实际值]。问题只是数组的左半部分是有序的(见图),并且该方法永远不会退出(无限循环)。在从http://www.java2novice.com/java-sorting-algorithms/quick-sort/ 进行 Java 转换之后(在 Java 中测试并且确实有效)。无法解决错误。

var arr = [Int]()
var length = 0

override func viewDidLoad()
{
    super.viewDidLoad()

    arr = [5,2,4,7,2,1,3,9,10,11,7,8]
    print(arr)

    sort(inputArr: &arr);

    print(arr)
}

func sort(inputArr: inout [Int])
{

    if (inputArr.count == 0) || (inputArr == nil)
    {
        print("finished")
        return
    }

    arr = inputArr
    length = arr.count
    quicksort(low:0,high:length-1)

}

func quicksort(low: Int, high: Int)
{
    var i = low
    var j = high

    var piv = (low + (high-low))
    piv = piv / 2
    let pivot_location = arr[piv]

    print("----")

    while i <= j
    {
        while arr[i] < pivot_location
        {
            i+=1
        }
        while arr[j] > pivot_location
        {
            j-=1
        }

        if i <= j
        {
            let temp = arr[i]
            arr[i] = arr[j]
            arr[j] = temp

            i+=1
            j-=1
        }
    }

    print(arr)

    if low < j
    {
        quicksort(low: low, high: j)
    }
    if i < high
    {
        quicksort(low: i, high: high)
    }
}

通过方法迭代后数组的控制台打印

【问题讨论】:

  • 因为您的数组很大,所以数组函数 .sorted 不适合您? inputArr.sorted { $0 > $1 } 反之亦然
  • 内置的排序例程经过高度优化,通常可以提供非常好的结果。为什么不使用它们?您所要做的就是提供一个比较闭包,让您比较 2 个元素,系统会完成剩下的工作。
  • [快速排序,因为数组中有大约 15,000 个实际值] 这是为了解释为什么不使用内置排序?这个数据量不是特别大,也不是什么需要额外努力的东西。我不知道 swift 数组是如何实现排序的,但是如图here 所示,CFArray 针对不同的情况进行了高度优化,我希望 swift 的数组可以做类似的事情。
  • @vikingosegundo 不能撒谎,只写我自己的,因为它为我的任务赢得了更多积分
  • 积分有什么用?如果您想在某种课程中获得积分,我会创建一个通用排序,而不是仅针对整数的排序。

标签: java ios swift sorting quicksort


【解决方案1】:

Swift 中的快速排序:

func quicksort<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 quicksort(less) + equal + quicksort(greater)
}

以 15.000 个随机数为基准(生成数字后计算时间):

  • arr.filter{ $0 &lt; $1 } 需要:大约 0.30 分钟
  • 问题中的代码需要:大约 1,00 分钟
  • 我的回答中的快速排序需要:大约 2 分钟

见:

https://github.com/raywenderlich/swift-algorithm-club/tree/master/Quicksort

此处的其他快速排序类型:

https://github.com/raywenderlich/swift-algorithm-club/blob/master/Quicksort/Quicksort.swift

【讨论】:

  • 是的,我看了这个,但无法解决:什么是守卫、过滤器和 $ 的使用
【解决方案2】:

如果您愿意,可以使用此代码进行快速排序,代码更简洁且易于理解。试试看

func quickSort2(_ input: [Int], startIndex:Int, endIndex: Int)-> [Int] {
    var inputArray = input
    if startIndex<endIndex {
        let pivot = inputArray[endIndex]
        var index = startIndex

        for demo in startIndex..<endIndex {
            if inputArray[demo] <= pivot {
                (inputArray[index], inputArray[demo]) = (inputArray[demo], inputArray[index])
                index += 1
            }
        }
        (inputArray[index], inputArray[endIndex]) = (inputArray[endIndex], inputArray[index])
        inputArray = quickSort2(inputArray, startIndex: startIndex, endIndex: index-1)
        inputArray = quickSort2(inputArray, startIndex: index+1, endIndex: endIndex)
    }
    return inputArray
}

【讨论】:

    【解决方案3】:

    您对pivpivot_location 的计算是错误的。应该是:

    let piv = (low + (high - low) / 2)
    let pivot_location = arr[piv]    
    

    请注意,我在之前的计算中将除法移动了 2。

    输出:

    [5, 2, 4, 7, 2, 1, 3, 9, 10, 11, 7, 8]
    [1、2、4、7、2、5、3、9、10、11、7、8]
    [1、2、3、2、7、5、4、9、10、11、7、8]
    [1、2、2、3、7、5、4、9、10、11、7、8]
    [1、2、2、3、7、5、4、9、10、11、7、8]
    [1、2、2、3、7、5、4、8、7、11、10、9]
    [1、2、2、3、4、5、7、8、7、11、10、9]
    [1、2、2、3、4、5、7、8、7、11、10、9]
    [1、2、2、3、4、5、7、8、7、11、10、9]
    [1、2、2、3、4、5、7、7、8、11、10、9]
    [1、2、2、3、4、5、7、7、8、9、10、11]
    [1、2、2、3、4、5、7、7、8、9、10、11]

    【讨论】:

    • 非常感谢,非常感谢
    【解决方案4】:

    快速排序代码:

    func quickSort<T: Comparable>(_ array: [T]) -> [T] {
        if array.count < 2 {
            return array
        }
    
        let pivot = array[0]
        print("pivot : \(pivot)")
        
        let smaller = array.filter { $0 < pivot }
        let larger = array.filter { $0 > pivot }
    
        let result = quickSort(smaller) + [pivot] + quickSort(larger)
        print("result : \(result)")
      
        return result
    }
    

    【讨论】:

      【解决方案5】:

      详情

      • Swift 5.2、Xcode 11.4 (11E146)

      解决方案

      import Foundation
      
      enum QuickSortOrder { case lowToHight, hightToLow }
      
      // MARK: Quik Sort
      // time: n*log n
      // memory: n*log n
      
      extension RangeReplaceableCollection where Element: Comparable {
          func quickSort(by order: QuickSortOrder = .lowToHight) -> Self {
              guard count > 1 else { return self }
              let pivot = self[index(endIndex, offsetBy: -1)]
              var pivotCount = 0
              var lessThanPivot = Self()
              var greaterThanPivot = Self()
              forEach { element in
                  switch element {
                  case ..<pivot: lessThanPivot.append(element)
                  case pivot: pivotCount += 1
                  default: greaterThanPivot.append(element)
                  }
              }
      
              let equalPivot = Self(repeating: pivot, count: pivotCount)
      
              if equalPivot.count == count {
                  return equalPivot
              } else {
                  if lessThanPivot.first != nil {
                      switch order {
                      case .lowToHight: return Self(lessThanPivot.quickSort(by: order) + equalPivot + greaterThanPivot.quickSort(by: order))
                      case .hightToLow: return Self(greaterThanPivot.quickSort(by: order) + equalPivot + lessThanPivot.quickSort(by: order))
                      }
                  } else {
                      switch order {
                      case .lowToHight: return Self(equalPivot + greaterThanPivot.quickSort(by: order))
                      case .hightToLow: return Self(greaterThanPivot.quickSort(by: order) + equalPivot)
                      }
                  }
              }
          }
      }
      
      // MARK: Quik Sort Partition Algorithm
      // time: n*log n
      // memory: use the same array (0)
      
      extension MutableCollection where Element: Comparable {
      
          mutating
          func quickSorted(by order: QuickSortOrder = .lowToHight) {
              quickSortSlicing(left: startIndex, right: index(endIndex, offsetBy: -1), by: order)
          }
      
          private mutating
          func quickSortSlicing(left: Index, right: Index, by order: QuickSortOrder) {
              guard left < right else { return }
              let index = quickSortSliceElementsReordering(left: left, right: right, by: order)
              quickSortSlicing(left: left, right: self.index(index, offsetBy: -1), by: order)
              quickSortSlicing(left: index, right: right, by: order)
          }
      
          private mutating
          func quickSortSliceElementsReordering(left: Index, right: Index, by order: QuickSortOrder) -> Index {
              let distance = self.distance(from: left, to: right)
              let pivot = self[self.index(left, offsetBy: distance/2)]
              var leftIndex = left
              var rightIndex = right
              while leftIndex <= rightIndex {
                  switch order {
                  case .lowToHight:
                      while self[leftIndex] < pivot { leftIndex = index(leftIndex, offsetBy: 1) }
                      while self[rightIndex] > pivot {
                          rightIndex = index(rightIndex, offsetBy: -1)
                      }
                  case .hightToLow:
                      while self[leftIndex] > pivot { leftIndex = index(leftIndex, offsetBy: 1) }
                      while self[rightIndex] < pivot {
                          rightIndex = index(rightIndex, offsetBy: -1)
                      }
                  }
                  if leftIndex <= rightIndex {
                      self.swapAt(leftIndex, rightIndex)
                      leftIndex = index(leftIndex, offsetBy: 1)
                      rightIndex = index(rightIndex, offsetBy: -1)
                  }
              }
              return leftIndex
          }
      }
      

      用法

      // Option 1
      let sortedArray = array.quickSort()
      
      // Option 2
      let sortedArray = array.quickSort(by: .lowToHight) 
      
      // Option 3
      let sortedArray = array.quickSort(by: .hightToLow)
      
      // Option 4
      var array = [......]
      array.quickSorted()
      
      // Option 5
      var array = [......]
      array.quickSorted(by: .lowToHight)
      
      // Option 6
      var array = [......]
      array.quickSorted(by: .hightToLow)
      

      单元测试

      import XCTest
      //@testable import *_41909806
      
      protocol QuicSortTestable {
          associatedtype C: MutableCollection & RangeReplaceableCollection where C:Equatable, C.Element: Comparable, C.Index == Int
          typealias Element = C.Element
          func generateCollection() -> C
      }
      
      extension QuicSortTestable where Self: XCTestCase {
          func _test() {
              let collection = generateCollection()
              _test(collection: collection, sortBy: <, quickSortOrder: .lowToHight)
              //_test(collection: collection, sortBy: >, quickSortOrder: .hightToLow)
          }
      
          private func measureTime(of closure: (() -> Void)) -> TimeInterval {
              let start = ProcessInfo.processInfo.systemUptime
              closure()
              return ProcessInfo.processInfo.systemUptime - start
          }
      
          private func _test(collection: C, sortBy closure: (_ left: Element, _ right: Element) -> Bool, quickSortOrder: QuickSortOrder) {
              print("--------------------------\n-- Collection size: \(collection.count), sorting order: \(quickSortOrder)")
              var times = [String: TimeInterval]()
      
              var sortedCollection = C()
              times["collection.sorted(by:)"] = measureTime (of: { sortedCollection = collection.sorted(by: closure) as! Self.C })
      
              var quickSortedCollection: C!
              times["collection.quickSort(by:)"] = measureTime (of: { quickSortedCollection = collection.quickSort(by: quickSortOrder) })
              XCTAssertEqual(quickSortedCollection, sortedCollection)
      
              quickSortedCollection = collection
              times["collection.quickSorted(by:)"] = measureTime (of: {
                  quickSortedCollection.quickSorted(by: quickSortOrder)
              })
              XCTAssertEqual(quickSortedCollection!, sortedCollection)
      
              quickSortedCollection = collection
              times["collection.quickSorted(by:)"] = measureTime (of: {
                  quickSortedCollection.quickSorted(by: quickSortOrder)
              })
              XCTAssertEqual(quickSortedCollection!, sortedCollection)
      
              let numberFormatter = NumberFormatter()
              numberFormatter.numberStyle = .decimal
              numberFormatter.maximumFractionDigits = 6
              times.forEach { key, value in
                  print("-- Excecution time of \(key) = \(numberFormatter.string(from: NSNumber(value: value)) ?? "\(value)")")
              }
          }
      }
      
      //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
      
      class SortingAlgorithmsTests_emptyCollection: XCTestCase, QuicSortTestable {
          func generateCollection() -> [Int] { [] }
          func test() { _test() }
      }
      
      class SortingAlgorithmsTests_oneElementCollection: SortingAlgorithmsTests_emptyCollection {
          override func generateCollection() -> [Int] { [1] }
      }
      
      class SortingAlgorithmsTests_dublicatedElementCollection: SortingAlgorithmsTests_emptyCollection {
          override func generateCollection() -> [Int] { [1,1] }
      }
      
      class SortingAlgorithmsTests_randomCollection: SortingAlgorithmsTests_emptyCollection {
          var generatedArraySize: Int { return 1_000 }
          override func generateCollection() -> [Int] {
              return (0 ..< generatedArraySize).map { (0...1000).randomElement() ?? $0 }
          }
      }
      
      class SortingAlgorithmsTests_randomCollection2: SortingAlgorithmsTests_randomCollection {
          override var generatedArraySize: Int { return 100_000 }
      
      }
      
      class SortingAlgorithmsTests_randomCollection3: SortingAlgorithmsTests_randomCollection {
          override var generatedArraySize: Int { return 1_000_000 }
      }
      

      时间测量(单元测试日志)

      --------------------------
      -- Collection size: 2, sorting order: lowToHight
      -- Excecution time of collection.quickSort(by:) = 0.00008
      -- Excecution time of collection.quickSorted(by:) = 0.000003
      -- Excecution time of collection.sorted(by:) = 0.000109
      --------------------------
      -- Collection size: 0, sorting order: lowToHight
      -- Excecution time of collection.quickSort(by:) = 0.000001
      -- Excecution time of collection.quickSorted(by:) = 0
      -- Excecution time of collection.sorted(by:) = 0.000007
      --------------------------
      -- Collection size: 1, sorting order: lowToHight
      -- Excecution time of collection.sorted(by:) = 0.00001
      -- Excecution time of collection.quickSort(by:) = 0.000002
      -- Excecution time of collection.quickSorted(by:) = 0.000001
      --------------------------
      -- Collection size: 1000, sorting order: lowToHight
      -- Excecution time of collection.quickSort(by:) = 0.009461
      -- Excecution time of collection.sorted(by:) = 0.008551
      -- Excecution time of collection.quickSorted(by:) = 0.006646
      --------------------------
      -- Collection size: 100000, sorting order: lowToHight
      -- Excecution time of collection.quickSorted(by:) = 0.892118
      -- Excecution time of collection.sorted(by:) = 0.59183
      -- Excecution time of collection.quickSort(by:) = 0.588457
      --------------------------
      -- Collection size: 1000000, sorting order: lowToHight
      -- Excecution time of collection.quickSort(by:) = 5.857336
      -- Excecution time of collection.sorted(by:) = 6.957055
      -- Excecution time of collection.quickSorted(by:) = 10.518193
      

      【讨论】: