Heapsort是另一种基于比较的算法,它使用堆对数组进行升序排序。本章建立在第22章 "堆 "中介绍的堆概念之上。
Heapsort利用了堆的优势,根据定义,堆是一个部分排序的二叉树,具有以下特点。
在一个最大堆中,所有的父节点都比它们的子节点大。
在最小堆中,所有父节点都比子节点小。下图显示了一个父节点值下划线的堆。
打开启动器playground。这个playground已经包含了一个最大堆的实现。你的目标是扩展Heap,使其也能进行排序。在你开始之前,让我们看一个堆排序工作的视觉例子。
对于任何给定的未经排序的数组,要从低到高排序,堆排序必须首先将这个数组转换为最大堆。
这个转换是通过筛选所有的父节点来完成的,最终在正确的位置。得到的最大堆是。
这与以下数组相对应。
因为单个筛下操作的时间复杂度是O(log n),所以建立一个堆的总时间复杂度是O(n log n)。
让我们来看看如何对这个数组进行升序排序。
因为最大堆中最大的元素总是在根部,所以你开始将索引0的第一个元素与索引n-1的最后一个元素进行交换。因此,下一步是将新的根音符5向下筛选,直到它落在正确的位置。
注意,你排除了堆的最后一个元素,因为你不再认为它是堆的一部分,而是排序的数组的一部分。
作为筛选5的结果,第二大元素21成为新的根。现在你可以重复之前的步骤,将21与最后一个元素6交换,缩小堆并向下筛选6。
你开始看到一个模式了吗?堆排序是非常简单明了的。当你交换第一个和最后一个元素时,较大的元素会以正确的顺序移到数组的后面。你重复交换和筛选的步骤,直到你达到一个大小为1的堆。
然后,该数组被完全排序。
注意:这个排序过程与第26章的选择排序非常相似。
接下来,你要实现这个排序算法。实际的实现非常简单,因为沉重的工作已经由siftDown方法完成。
下面是发生的事情。
你首先制作一个堆的副本。在heap sort对elements数组进行排序后,它就不再是一个有效的heap了。通过在堆的副本上工作,你可以确保堆保持有效。
你循环浏览数组,从最后一个元素开始。
将第一个元素和最后一个元素交换。这个交换将最大的未排序的元素移到它的正确位置。
因为现在的堆是无效的,你必须从新的根节点开始筛选。结果是,下一个最大的元素将成为新的根节点。
注意:为了支持堆排序,你已经在siftDown方法中加入了upTo参数。这样一来,向下筛选只使用数组中未排序的部分,而数组会随着每次循环迭代而缩小。
最后,让你的新方法试一试。
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]
尽管你从内存排序中获益,但堆排序的性能在最好、最差和平均情况下都是O(n log n)。这种性能的统一性是因为你必须对整个列表进行一次遍历,而且,每次交换元素时,你必须进行一次筛选,这是一个O(log n)的操作。
堆排序也不是一个稳定的排序,因为它取决于元素是如何布置和放入堆的。例如,如果你按等级对一副牌进行堆排序,你可能会看到它们的套牌与原来的牌相比,顺序发生了变化。
Heapsort利用最大堆数据结构对数组中的元素进行排序。
Heapsort通过遵循一个简单的模式对其元素进行排序。
交换第一个和最后一个元素。
从根部开始进行筛选,以满足作为一个堆的要求。
将数组的大小减少一个,因为最后的元素将是最大的元素。
重复这些步骤,直到你到达数组的起点。
上一章 | 目录 | 下一章 |
---|