一、堆(Heap)定义
(1)n个关键字序列Kl,K2,…,Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):
k(i)<=k(2i)且k(i)<=k(2i+1)(1≤i≤ n/2),
当然,这是小根堆,大根堆则换成>=号。
(2)k(i)相当于二叉树的非叶子结点,K(2i)则是左子节点,k(2i+1)是右子节点
若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:
树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
二、堆排序的思想
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
三、算法实现
/** * 堆排序 * @param data 数据队列 */ public static void heapSort(int[] data) { //初始化大堆 for (int i = data.length / 2 - 1; i >= 0; i--) { adjustHeap(data, i, data.length); //指定父节点堆调整 } int temp; //临时空间 for (int i = data.length - 1; i > 0; i--) { //堆首与堆尾交换 temp = data[i]; data[i] = data[0]; data[0] = temp; //大堆调整 adjustHeap(data, 0, i); } } /** * 堆调整 * @param data 数据队列 * @param start 起始位置 * @param end 截止位置,是堆尾的下一个位置 */ public static void adjustHeap(int[] data, int start, int end) { int src=data[start]; //保存起始位置的值 for (int i = start * 2 + 1; i < end; i = i * 2 + 1) { //判断左右节点大小 if (i + 1 < end && data[i] < data[i + 1]) { i++; //右节点大 } //起始位置值最大 if(src >= data[i]) break; data[start]=data[i];//赋值最大值 start=i; //记录大值的位置 } data[start]=src;//回填起始值 }
四、算法复杂度
堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用adjustHeap实现的。
平均性能O(N*logN),由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1).
它是不稳定的排序方法。(排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前 和排序后他们的相对位置不发生变化)