堆排序
基本思想
堆的概念
堆是一棵顺序存储的完全二叉树。
小顶堆:每个结点的关键字都不大于其孩子结点的关键字。
大顶堆:每个结点的关键字都不小于其孩子结点的关键字。
举例来说,对于n个元素的序列{R0, R1, ... , Rn}当且仅当满足下列关系之一时,称之为堆:
(1)Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)
(2)Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)
其中i=1,2,…,n/2向下取整;
堆的存储
一般都用数组来表示堆,i结点的父结点下标就为(i–1)/2。它的左右子结点下标分别为2*i+1和2*i+2。如第0个结点左右子结点下标分别为1和2。
堆的插入
一般直接将元素放在数组的后面。
堆的调整
堆排序的概念:
堆排序是在直接选择排序的基础上借助于堆的一种排序方法。在选择排序中,为找出最小的记录需要作n-1次比较,但比较的信息没有保存下来,然后为寻找关键字次小的记录要对剩下的n-1个记录进行n-2次比较,如此反复多次,增加了时间的开销。堆排序是在寻找当前最大元素的同时,还保存了本趟排序过程所产生的其他比较信息,这些信息就存放在堆中。
操作方法
操作流程图:
算法实现
// 堆排序算法
publicstaticvoid heapSort(int[]array) {
buildHeap(array);// 构建堆
intn= array.length;
inti= 0;
for (i= n - 1; i >= 1; i--){
swap(array, 0, i);
heapify(array, 0, i);
}
}
publicstaticvoid buildHeap(int[]array) {
intn= array.length;// 数组中元素的个数
for (inti = n/ 2 - 1; i >= 0; i--)//从第一个有孩子的节点开始,进行堆调整
heapify(array, i,n);
}
publicstaticvoid heapify(int[]A, intidx,intmax) {
intleft= 2 * idx + 1;// 左孩子的下标(如果存在的话)
intright= 2 * idx + 2;// 左孩子的下标(如果存在的话)
intlargest= 0;// 寻找3个节点中最大值节点的下标
if (left< max && A[left]> A[idx])//在不越界的情况下左节点的值大于根节点的值
largest = left;//将左孩子的值赋给最大值
else
largest = idx;//如果不符合,最大值为自身的值
if (right< max && A[right]> A[largest])//在不越界的情况下右节点的值大于根节点的值
largest= right;
if (largest!= idx) {
swap(A, largest,idx);
heapify(A, largest,max);
}
}
效率分析
算法性能:
时间复杂度:
因为堆所对应的二叉树为完全二叉树,而完全二叉树通常采用顺序存储方式,所以堆一般也采用顺序存储。当想得到一个序列中k个最小的元素之前的部分排序序列,最好采用堆排序。因为堆排序的时间复杂度是O(n+klog2n),若k≤n/log2n,则可得到的时间复杂度为O(n)。
空间复杂度
堆排序需要占用 1 个临时空间,在交换数值时使用。
算法稳定性
堆排序是一种不稳定的排序方法。因为在堆的调整过程中,关键字进行比较和交换所走的是该结点到叶子结点的一条路径,因此对于相同的关键字就可能出现排在后面的关键字被交换到前面来的情况。