使用 Swift 实现归并排序

栏目: Swift · 发布时间: 5年前

内容简介:| 作者:Jimmy M Andersson| 链接:

原文链接

| 作者:Jimmy M Andersson

| 链接: medium.com/swlh/sortin…

之前介绍了堆排序,这是一种基于堆的 排序 算法。今天,我们进一步深入研究排序算法,来看看归并排序,这是一种时间复杂度为 O(n * log(n)) 的排序算法,作为权衡,其空间复杂度为 O(n)

本文的具体实现可以查看相应的 XCode Playground 文件

什么是归并排序?

归并排序属性分治法一类的算法。分治法基本思想是解决只包含一半数据的问题要容易得多,因此可以尝试将数据递归地分割成两个大小相等的数据集来处理。

在归并排序中,对原始数组大小一半的数据进行排序要容易得多。将原始数组分割成四个四分之一大小的数组分别排序会更简单,因此我们执行递归分割操作。这是该算法巧妙的地方。由于我们处理的是有限大小的数组,所以最终分割得到的所有数组大小都会少于两个元素,而这样的数组可以看作是已排序的。

当到达上面所说的状态时,递归终止,我们开始展开调用堆栈。在这个过程中,我们通过比较每个数组中的元素,并将它们插入到一个新数组中来合并数组,然后将这些数组返回给调用堆栈的下一帧。来看看下面的草图,了解它的工作原理:

使用 Swift 实现归并排序

构造算法

我们将采用面向协议的方法,这意味着我们将扩展包含符合 Comparable 协议的元素的数组的 Array 实现。我们还将实现一个返回数组的排序副本的方法,而不是替换我们调用方法的数组。如下代码所示:

extension Array where Element: Comparable {
 public mutating func mergeSort() {
    let startSlice = self[0..<self.count]
    let slice = mergeSort(startSlice)
    let array = Array(slice)
    self = array
  }
  
  public func mergeSorted() -> Array<Element> {
    let startSlice = self[0..<self.count]
    let slice = mergeSort(startSlice)
    let array = Array(slice)
    return array
  }
}
复制代码

请注意,我们声明了一个名为 startSlice 的变量。我们将使用一个名为 ArraySlice 的泛型结构,它将帮助我们避免在调用堆栈中对数组进行不必要的复制。相反,我们将创建一个已分配内存的视图,并只在我们将两个切片合并在一起时才分配新内存。当对 .mergeSort(_:) 的调用返回时,我们需要将它转换为一个 Array ,以便与我们的返回类型兼容。但是,请注意,Swift 编译器是一个非常智能的构造,它只是让新的 Array 对象使用 ArraySlice 中已经分配的内存,因此不需要任何开销。

接下来,我们定义了 .mergeSort(_:) 方法,它仍然在扩展范围内:

private func mergeSort(_ array: ArraySlice<Element>) -> ArraySlice<Element> {
    if array.count < 2 {
      return array
    } else {
      let midIndex = (array.endIndex + array.startIndex) / 2
      let slice1 = mergeSort(array[array.startIndex..<midIndex])
      let slice2 = mergeSort(array[midIndex..<array.endIndex])
      return merge(slice1, slice2)
    }
  }
复制代码

这里包含我们决定是否可以将数组的分割部分视为“已排序”的部分。如果数组的元素少于两个元素,则按定义是已排序的,因此我们只返回相同的数组。如果它包含更多元素,我们通过创建两个新的 ArraySlice 对象并将它们传递给递归调用再次拆分它。当两个递归调用返回时,我们可以确定我们有两个排序的数组切片可以使用,所以我们调用方法 merge(_:_:) 将两个切片合并为一个并返回它。 merge(_:_:) 看起来像这样:

private func merge(_ firstArray: ArraySlice<Element>, _ secondArray: ArraySlice<Element>) -> ArraySlice<Element> {
    var newArray = ArraySlice<Element>()
    newArray.reserveCapacity(firstArray.count + secondArray.count)
    var index1 = firstArray.startIndex
    var index2 = secondArray.startIndex
    
    while index1 < firstArray.endIndex && index2 < secondArray.endIndex {
      if firstArray[index1] < secondArray[index2] {
        newArray.append(firstArray[index1])
        index1 += 1
      } else {
        newArray.append(secondArray[index2])
        index2 += 1
      }
    }
    
    if index1 < firstArray.endIndex {
      let range = index1..<firstArray.endIndex
      let remainingElements = firstArray[range]
      newArray.append(contentsOf: remainingElements)
    }
    if index2 < secondArray.endIndex {
      let range = index2..<secondArray.endIndex
      let remainingElements = secondArray[range]
      newArray.append(contentsOf: remainingElements)
    }
    
    return newArray
  }
复制代码

这个有点棘手,所以请花时间阅读并理解它。我们首先创建一个新的 ArraySlice 对象和两个变量,以跟踪我们当前在每个切片的索引。然后我们比较每个切片的元素,将最小的元素放入我们的新 ArraySlice 对象中,直到完成其中一个原始切片。最后,我们检查是否还有剩余的元素需要插入。一旦完成,我们将新切片返回到调用函数,我们就完成了排序操作。

关注我们

欢迎关注我们的公众号:iOS-Tips,也欢迎加入我们的群组讨论问题。可以加微信 coldlight_hh / wsy9871 进入我们的 iOS / flutter 微信群。

使用 Swift 实现归并排序

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

众包

众包

杰夫·豪 / 牛文静 / 中信出版社 / 2009-6 / 36.00元

本书是继《长尾理论》之后的重要商业书籍。本书回答了《长尾理论》遗留的一大悬念。在长尾中作者详细阐述了长尾之所以成为可能的一个基础,但是没有详细解读,本书就是对这一悬念的详细回答,是《长尾理论》作者强力推荐的图书,在国际上引起了不小的轰动,“众包”这一概念也成为一个标准术语被商界广泛重视。本书大致分为三个部分,介绍众包的现在、过去和未来,解释了它的缘起、普遍性、力量以及商业上的适用性,通俗易懂,精彩......一起来看看 《众包》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具