堆排序
堆排序是一种树形选择排序,在排序过程中,将待排序的记录R[1...n]看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序的序列中选择关键字最大(或最小)的记录。
堆的定义
堆实质上是满足如下性质的完全二叉树:树中所有非终端节点的值均不大于(或不小于)其左,右孩子节点的值。
(2)数据序列为9,30,49,46,58,79,将其转换为一颗完全二叉树
(3)数据序列为93,82,76,63,58,67,55,将其转换为一颗完全二叉树
算法思路
(1)建堆
(2)将堆的根节点和最后一个节点交换
堆排序的关键在于建堆,它按如下步骤完成排序:
第一趟:将索引0~n-1 处的全部数据建成大顶(或小顶)堆,就可以选择出这组数据中最大(或最小)值。
将上一步所建的大顶(或小顶)堆的根节点与这组数据的最后一个节点交换,就使得这组数据的最大(或最小)值排在最后。
第二趟:将索引0~n-2 处的全部数据建成大顶(或小顶)堆,就可以选择出这组数据中最大(或最小)值。
将上一步所建的大顶(或小顶)堆的根节点与这组数据倒数第2个节点交换,就使得这组数据的最大(或最小)值排在倒数第2位。
.
.
.
第k趟:将索引0~n-k 处的全部数据建成大顶(或小顶)堆,就可以选择出这组数据中最大(或最小)值。
将上一步所建的大顶(或小顶)堆的根节点与这组数据倒数第k个节点交换,就使得这组数据的最大(或最小)值排在倒数第k位。
对于包含n个数据元素的数据组而言,堆排序需要经过n-1次建堆,每次建堆的作用就是选出当前堆的最大值或最小值。堆排序本质上依然是一种选择排序。
堆排序算法实现
/***
* 堆排序
* @param data 待排序数组
*/
public static void heapSort(int[] data){
//循环建堆
for(int i = 0; i<data.length-1; i++){
//建堆
buildHeap(data,data.length-1-i);
//交换堆顶和最后一个元素
swap(data,0,data.length-1-i);
}
}
/***
* 对 data 数组从 0 到 lastIndex 建大顶堆
* @param data 待排序数组
* @param lastIndex 未排序数组的最后一个元素的索引
*/
public static void buildHeap(int[] data,int lastIndex){
//从 lastIndex 处节点(最后一个节点) 的父节点开始
//只要保证每个非叶子节点的值大于等于其左,右子节点的值就行
for(int i = (lastIndex-1)/2; i>=0; i--){
// k 保存当前正在判断的节点
int k = i;
//如果 当前k 节点的子节点存在
while(2*k+1 <= lastIndex){
// k节点的左孩子节点的索引
int biggerIndex = 2*k+1;
// 如果k节点的右孩子存在
if(biggerIndex < lastIndex){
//如果右孩子节点的值较大
if(data[biggerIndex]<data[biggerIndex+1]) {
// biggerIndex总是记录较大子节点的索引
biggerIndex++;
}
}
//如果 k 节点的值小于其较大子节点的值
if(data[k]<data[biggerIndex]){
//交换
swap(data,k,biggerIndex);
//将biggerIndex 赋给k, 开始while 循环的下一次循环
// 重新保证 k节点的值大于其左,右子节点的值
k = biggerIndex;
}
else break;
}
}
}
/***
* 交换data数组中 i,j 两个索引处的元素
* @param data
* @param i
* @param j
*/
public static void swap(int[] data, int i, int j){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
测试代码
算法分析
(1)时间复杂度:O(nlogn)
(2)空间复杂度:O(1)
(3)是不稳定排序
(4)只能用于顺序结构,不能用于链式结构
(5)当记录较多时较为高效