利用二叉堆的有序性质,可以利用其进行排序。堆排序分为以下两步:
1 重排列元素数组构成一个二叉堆。
2 不断返回最大元素(即根节点)直到堆为空
注:对于堆的方法可参考文章https://blog.csdn.net/Raine_Yang/article/details/122063624
1 从下至上构造堆
当然,我们可以对每个元素调用之前实现的insert()方法,但是这么做每一次定位都需要查找整个堆。一个更高效的查找方法是自下而上。
如图,我们可以把E-L-E这一部分当做一个子堆。我们逐层对各个子堆进行排序,这样得到最终整个堆也是有序的
for (int i = N / 2; i >= 1; i--) {
sink(i);
}
如程序所示,从数组末尾子节点的父节点(N / 2)处开始,对各个子堆的根节点进行sink操作。在例图中将会遍历E, T, R, O, S,至此,堆构造完成。
2 下沉排序
对于构造完成的堆,根节点为最大值。因此我们首先将根节点和最末尾元素交换,此时最末尾元素排成,然后对新的根节点再进行sink操作以保证堆有序。重复此操作直至遍历完成所有元素。
注意这一方法类似于对各个元素调用优先队列里delMax方法,唯一不同的是我们不是把得到的最大值返回,而是存在数组末尾。
while (N > 1) {
exch(1, N--);
sink(1);
}
整个堆排序代码为:
// heap sort
public void sort() {
for (int i = N / 2; i >= 1; i--) {
sink(i);
}
while (N > 1) {
exch(pq, 1, N--);
sink(1);
}
}
堆排序的性能:
构造堆操作时间复杂度为N,排序操作时间复杂度NlogN,因此整个排序时间复杂度为2NlogN
堆排序一大优点是它为原地排序,并且最糟情况下依然保存O(NlogN)时间复杂度级别。这一优势是快速排序和合并排序两大经典排序都不具备的(合并排序空间复杂度N,快速排序最糟情况时间复杂度N²)
但是堆排序也有一些缺陷。尽管同为NlogN级别时间复杂度,堆排序单次操作用时稍大于快速排序。并且堆排序由于要大幅在树上各索引跳跃,无法充分利用缓存,导致实际实施的效率低于理论值。另外,由于堆排序涉及长距离交换,它不稳定