使用 Swift 实现堆排序

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

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

| 作者:Jimmy M Andersson

| 链接: medium.com/appcoda-tut…

| 公众号链接: mp.weixin.qq.com/s/kfqsTnJHb…

排序是计算机的一项主要任务。这并不是因为 排序 本身非常有趣,而是因为很多其它算法依赖于排序才能正常运行。本文主要描述如何实现堆排序算法,该算法依赖于称为堆的数据结构。

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

堆是一个完整且部分排序的二叉树。通俗一点讲就是它总是将新数据节点插入最深一层的左侧。从某种意义上讲,元素间是有顺序的,尽管实际上并未做排序操作。

在本教程中,我们将使用最大堆(Max Heap)实现一个始终在 O(n * log(n)) 时间内运行的排序算法。首先,我们将实现一个 SortingAlgorithms 类,该类带有一个对数组进行排序的静态函数,然后再使用面向协议的解决方案。

关于最大堆,有三个重要事项:

  • 始终保证树根中的元素值最大;
  • 任何一个节点如果有子节点的话,它的值总是大于它所有的子节点的值;
  • 堆通常可以以数组的形式实现,其中可以使用非常简单的数学公式来计算特定索引的父节点和子节点。这将使我们能够实现快速有效的排序。

代码

首先,我们来扩展 Swift 标准库的 Int 类型的实现,以抽象出获取父节点和子节点索引的运算公式。如下代码所示:

private extension Int {
  var parent: Int {
    return (self - 1) / 2
  }
  
  var leftChild: Int {
    return (self * 2) + 1
  }
  
  var rightChild: Int {
    return (self * 2) + 2
  }
}
复制代码

使用这些代码,我们可以通过计算属性来计算索引,而不是需要时直接用数学公式来计算。这样保证了可读性。另外这是一个私有扩展,意味着它不会影响 Int 的整体性,而只是在该文件作用域中可用。

接下来,让我们分步来说明 排序算法 的各个步骤。

首先,我们将从数组构建一个最大堆。基本操作是将新元素插入到堆的末尾再交换到正确的位置,因此我们可以使用简单的循环来模拟插入操作。我们先假定数组只有两个元素,“堆积”这两个元素,在循环的每个迭代中,我们插入一个元素并重新堆积。如下所示:

使用 Swift 实现堆排序

操作完成后,数组看上去并没有特意排序。事实上,看起来更糟糕。这是因为我们使用数组存储了树的节点。看一下操作前后的对比:

使用 Swift 实现堆排序

看上去我们只是交换了元素,但事实上,我们刚刚建立了一个将用于完成排序的属性。

获取第一个元素,然后将其与最后一个元素交换,我们可以把最大的元素放到最后。然后假定数组长度减 1,然后重新堆积这个子数组,又可以得到这个子数组的最大元素。然后将子数组的最大元素放到子数组最后,这样依此类推,就可以得到一个完全排序的数组。

使用 Swift 实现堆排序

我们的 .heapSort(_:) 方法的代码如下,包括构建和缩小堆。

class SortingAlgorithms {
  private init() {}
  
  public static func heapSort<DataType: Comparable>(_ array: inout [DataType]) {
    if array.count < 2 { return }
    buildHeap(&array)
    shrinkHeap(&array)
  }
  
  private static func buildHeap<DataType: Comparable>(_ array: inout [DataType]) {
    for index in 1..<array.count {
      var child = index
      var parent = child.parent
      while child > 0 && array[child] > array[parent] {
        swap(child, with: parent, in: &array)
        child = parent
        parent = child.parent
      }
    }
  }
  
  private static func shrinkHeap<DataType: Comparable>(_ array: inout [DataType]) {
    for index in stride(from: array.count - 1, to: 0, by: -1) {
      swap(0, with: index, in: &array)
      var parent = 0
      var leftChild = parent.leftChild
      var rightChild = parent.rightChild
      while parent < index {
        var maxChild = -1
        if leftChild < index {
          maxChild = leftChild
        } else {
          break
        }
        if rightChild < index && array[rightChild] > array[maxChild] {
          maxChild = rightChild
        }
        guard array[maxChild] > array[parent] else { break }
        
        swap(parent, with: maxChild, in: &array)
        parent = maxChild
        leftChild = parent.leftChild
        rightChild = parent.rightChild
      }
    }
  }
  
  private static func swap<DataType: Comparable>(_ firstIndex: Int, with secondIndex: Int, in array: inout [DataType]) {
    let temp = array[firstIndex]
    array[firstIndex] = array[secondIndex]
    array[secondIndex] = temp
  }
}
复制代码

这就是我们想要的,不过我们可以让它更干净一些。

面向协议的实现

通过扩展可比较元素类型的 Array 类型,我们能得到一些好处。一个是代码量更少,另一个是可以直接在对象上调用方法,而不需要如下处理:

SortingAlgorithms.heapSort(&myArray)
复制代码

而是这样:

myArray.heapSort()
复制代码

这样更加清晰。元素类型不符合 Comparable 协议时,编辑器甚至不会在数组对象上智能提示 .heapSort()。

public extension Array where Element: Comparable {
  public mutating func heapSort() {
    buildHeap()
    shrinkHeap()
  }
  
  private mutating func buildHeap() {
    for index in 1..<self.count {
      var child = index
      var parent = child.parent
      while child > 0 && self[child] > self[parent] {
        swapAt(child, parent)
        child = parent
        parent = child.parent
      }
    }
  }
  
  private mutating func shrinkHeap() {
    for index in stride(from: self.count - 1, to: 0, by: -1) {
      swapAt(0, index)
      var parent = 0
      var leftChild = parent.leftChild
      var rightChild = parent.rightChild
      while parent < index {
        var maxChild = -1
        if leftChild < index {
          maxChild = leftChild
        } else {
          break
        }
        if rightChild < index && self[rightChild] > self[maxChild] {
          maxChild = rightChild
        }
        guard self[maxChild] > self[parent] else { break }
        
        swapAt(parent, maxChild)
        parent = maxChild
        leftChild = parent.leftChild
        rightChild = parent.rightChild
      }
    }
  }
}
复制代码

Done!!!


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

查看所有标签

猜你喜欢:

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

编程之美:微软技术面试心得

编程之美:微软技术面试心得

《编程之美》小组 / 电子工业出版社 / 2018-9 / 79

《编程之美:微软技术面试心得》收集了约60道算法和程序设计的题目,这些题目大部分在微软的笔试、面试中出现过,有的曾被微软员工热烈地讨论过。作者试图从书中各种有趣的问题出发,引导读者发现问题、分析问题、解决问题,寻找更优的解法。《编程之美:微软技术面试心得》内容分为以下几个部分。 游戏之乐:从游戏和其他有趣问题出发,化繁为简,分析总结。 数字之魅:编程的过程实际上就是和数字及字符打交道的......一起来看看 《编程之美:微软技术面试心得》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

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

在线图片转Base64编码工具