美文网首页算法
排序算法(一) —— 堆排序之一个简单示例(一)

排序算法(一) —— 堆排序之一个简单示例(一)

作者: 刀客传奇 | 来源:发表于2018-08-22 12:26 被阅读34次

    版本记录

    版本号 时间
    V1.0 2018.08.22

    前言

    排序算法是最常见的算法,其中包括了冒泡、选择等很多不同的排序算法,接下来几篇就会介绍相应的排序算法,其实前面几篇已经有所涉及了,以后有些东西我会慢慢移动和增加到这个专题里面。

    开始

    首先看一下本文写作环境。

    写作环境:Swift 4, iOS 11, Xcode 9

    Heapsort是另一种基于比较的算法,它使用堆按升序对数组进行排序。 根据定义,Heapsort利用了一个堆,它具有以下特性的部分排序的二叉树:

    • 在最大堆中,所有父节点都大于其子节点。
    • 在最小堆中,所有父节点都小于其子节点。

    下图显示了父节点值带下划线的堆:


    原理

    对于任何给定的未排序数组,要从最低到最高排序,堆排序必须首先将此数组转换为最大堆。

    通过筛选所有父节点来完成此转换,使它们最终位于正确的位置。 生成的最大堆是:

    其中对应以下数组:

    由于单个筛选操作的时间复杂度为O(log n),因此构建堆的总时间复杂度为O(n log n)

    我们来看看如何按升序对此数组进行排序。

    因为最大堆中的最大元素始终位于根,所以首先将索引0处的第一个元素与索引n - 1处的最后一个元素进行交换。作为此交换的结果,数组的最后一个元素位于正确的位置,但堆现在无效。 因此,下一步是将新的根节点5向下筛,直到它落在正确的位置。

    请注意,您排除堆的最后一个元素,因为我们不再将其视为堆的一部分,而是排好序的数组。

    作为筛选5的结果,第二大元素21成为新根。 您现在可以重复前面的步骤,使用最后一个元素6交换21,缩小堆并向下筛选6。

    堆排序非常简单。 当你交换第一个和最后一个元素时,较大的元素以正确的顺序进入数组的后面。 您只需重复交换和筛选步骤,直到达到大小为1的堆。然后对阵列进行完全排序。


    Implementation - 具体实现

    接下来,您将实现此排序算法。 实际的实现非常简单,因为siftDown方法已经完成了繁重的工作:

    extension Heap {
      func sorted() -> [Element] {
        var heap = Heap(sort: sort, elements: elements) // 1
        for index in heap.elements.indices.reversed() { // 2
          heap.elements.swapAt(0, index) // 3
          heap.siftDown(from: 0, upTo: index) // 4
        }
        return heap.elements
      }
    }
    

    下面进行详细说明:

    • 1)您首先制作堆的副本。 堆排序对元素数组进行排序后,它不再是有效的堆。 通过处理堆的副本,可以确保堆保持有效。
    • 2)从最后一个元素开始循环遍历数组。
    • 3)您交换第一个元素和最后一个元素。 这会将最大的未排序元素移动到其正确的位置。
    • 4)由于堆现在无效,因此必须对新的根节点进行筛选。 结果,下一个最大的元素将成为新的根。

    请注意,为了支持堆排序,您已向siftDown方法添加了一个额外的参数upTo。 这样,sift down仅使用数组的未排序部分,该部分随着循环的每次迭代而缩小。

    最后,尝试一下您的新方法:

    let heap = Heap(sort: >, elements: [6, 12, 2, 26, 8, 18, 21, 9, 5])
    print(heap.sorted())
    

    看一下输出打印

    [2, 5, 6, 8, 9, 12, 18, 21, 26]
    

    Performance - 性能

    即使您获得了内存中排序的好处,堆排序的性能也是O(n log n)的最佳,更差和平均情况。 这是因为您必须遍历整个列表一次,并且每次交换元素时都必须执行向下筛选,这是一个O(log n)操作。

    堆排序也不是一种稳定的排序,因为它取决于元素的布局方式和放入堆中的方式。


    源码

    下面我们就一起看一下源码。

    struct Heap<Element: Equatable> {
      
      var elements: [Element] = []
      let sort: (Element, Element) -> Bool
      
      init(sort: @escaping (Element, Element) -> Bool, elements: [Element] = []) {
        self.sort = sort
        self.elements = elements
        
        if !elements.isEmpty {
          for i in stride(from: elements.count / 2 - 1, through: 0, by: -1) {
            siftDown(from: i, upTo: elements.count)
          }
        }
      }
      
      var isEmpty: Bool {
        return elements.isEmpty
      }
      
      var count: Int {
        return elements.count
      }
      
      func peek() -> Element? {
        return elements.first
      }
      
      func leftChildIndex(ofParentAt index: Int) -> Int {
        return (2 * index) + 1
      }
      
      func rightChildIndex(ofParentAt index: Int) -> Int {
        return (2 * index) + 2
      }
      
      func parentIndex(ofChildAt index: Int) -> Int {
        return (index - 1) / 2
      }
      
      mutating func remove() -> Element? {
        guard !isEmpty else {
          return nil
        }
        elements.swapAt(0, count - 1)
        defer {
          siftDown(from: 0, upTo: elements.count)
        }
        return elements.removeLast()
      }
      
      mutating func siftDown(from index: Int, upTo size: Int) {
        var parent = index
        while true {
          let left = leftChildIndex(ofParentAt: parent)
          let right = rightChildIndex(ofParentAt: parent)
          var candidate = parent
          if left < size && sort(elements[left], elements[candidate]) {
            candidate = left
          }
          if right < size && sort(elements[right], elements[candidate]) {
            candidate = right
          }
          if candidate == parent {
            return
          }
          elements.swapAt(parent, candidate)
          parent = candidate
        }
      }
      
      mutating func insert(_ element: Element) {
        elements.append(element)
        siftUp(from: elements.count - 1)
      }
      
      mutating func siftUp(from index: Int) {
        var child = index
        var parent = parentIndex(ofChildAt: child)
        while child > 0 && sort(elements[child], elements[parent]) {
          elements.swapAt(child, parent)
          child = parent
          parent = parentIndex(ofChildAt: child)
        }
      }
      
      mutating func remove(at index: Int) -> Element? {
        guard index < elements.count else {
          return nil // 1
        }
        if index == elements.count - 1 {
          return elements.removeLast() // 2
        } else {
          elements.swapAt(index, elements.count - 1) // 3
          defer {
            siftDown(from: index, upTo: elements.count) // 5
            siftUp(from: index)
          }
          return elements.removeLast() // 4
        }
      }
      
      func index(of element: Element, startingAt i: Int) -> Int? {
        if i >= count {
          return nil
        }
        if sort(element, elements[i]) {
          return nil
        }
        if element == elements[i] {
          return i
        }
        if let j = index(of: element, startingAt: leftChildIndex(ofParentAt: i)) {
          return j
        }
        if let j = index(of: element, startingAt: rightChildIndex(ofParentAt: i)) {
          return j
        }
        return nil
      }
    }
    
    extension Heap {
      
      func sorted() -> [Element] {
        var heap = Heap(sort: sort, elements: elements)
        for index in heap.elements.indices.reversed() {
          heap.elements.swapAt(0, index)
          heap.siftDown(from: 0, upTo: index)
        }
        return heap.elements
      }
    }
    
    let heap = Heap(sort: >, elements: [6, 12, 2, 26, 8, 18, 21, 9, 5])
    print(heap.sorted())
    

    后记

    本篇主要讲述了堆排序,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:排序算法(一) —— 堆排序之一个简单示例(一)

        本文链接:https://www.haomeiwen.com/subject/eepeiftx.html