1、思想
堆即是一棵完全二叉树。堆排序的核心是堆调整算法。首先根据初始输入数据,利用堆调整算法shiftDown()形成最大堆;然后,将堆顶元素与堆尾元素交换,缩小堆的范围并重新调整为最大堆,如此往复。堆排序是一种不稳定的排序算法。
2、堆调整过程(将初始堆调整为最大堆)
最大堆定义:每个节点的值都大于或等于其左右孩子节点的值。
(1)对于初始堆,如下:
(2)此时我们从最后一个非叶子节点开始(叶节点自然不用调整,第一个非叶子节点的索引计算方法为arr.length/2-1=5/2-1,索引从0开始,即根节点的索引值为0),从右至左,从下至上进行调整。本图中最后一个非叶子节点是节点6。
应为【5,6,9】三个元素中,9元素最大,所以将6与9交换。
(3)找到第二个非叶节点4,由于【4,9,8】中9元素最大,4和9交换。
这时,交换导致了子根【4,5,6】的结构混乱,继续调整,【4,5,6】中6最大,交换4和6。
此时,我们就将一个初始堆调整为一个大顶堆。
3、代码
/**
* Title: 堆排序(选择排序),升序排序(最大堆),依赖于初始序列
* Description: 现将给定序列调整为最大堆,然后每次将堆顶元素与堆尾元素交换并缩小堆的范围,直到将堆缩小至1
* 时间复杂度:O(nlgn)
* 空间复杂度:O(1)
* 稳 定 性:不稳定
* 内部排序(在排序过程中数据元素完全在内存)
* @author rico
* @created 2017年5月25日 上午9:48:06
*/
public class HeapSort {
public static int[] heapSort(int[] target) {
//判断数组,即判断堆是否存在
if (target != null && target.length > 1) {
// 从初始堆的最后一个非叶子节点开始调整
int pos = target.length / 2-1;//因为索引从0开始,第N/2-1处(N为节点数量)的节点正好是二叉树的最后一个非叶子节点
while (pos >= 0) {
//从初始堆的最后一个非叶子节点开始,从右到左,从下到上的调整各个元素
shiftDown(target, pos, target.length - 1);
pos--;
}
// 经过一轮堆调整后,将大根堆的根元素与堆的最后一个元素交换
for (int i = target.length-1; i > 0; i--) {
int temp = target[i];//将最大堆的第一个元素和最后一个元素交换
target[i] = target[0];
target[0] = temp;
shiftDown(target, 0, i-1);//此时只要将第一个元素放到合适的位置就行,因为其他位置都已经是有序的了
}
return target;
}
return target;
}
/**
* @description 自上而下调整为最大堆,将需要调整的元素放在合适的位置
* @author rico
* @created 2017年5月25日 上午9:45:40
* @param target
* @param start
* @param end
*/
private static void shiftDown(int[] target, int start, int end) {//将节点i换到适合自己的位置,一直和子节点比较,
//一直向下走,直到不能走了为止
int i = start;
int j = 2 * start + 1;//节点i左孩子的索引
int temp = target[i];//从该元素开始调整堆
while (j <= end) { // 迭代条件
if (j < end && target[j + 1] > target[j]) { //找出较大子女,将节点i的左孩子和右孩子进行比较
j = j + 1; //右孩子比较大
}
if (target[j] <= temp) { // 父亲大于子女,这个temp是不变的,是要放置在正确位置的那个元素
break;//不操作
} else {//父亲小于子女
target[i] = target[j];//将子女中较大的值赋给父亲
i = j;//将较大的子节点作为根节点,比较该根节点和其子女和值大小,看是否需要调整。
j = 2 * j + 1;
}
}
target[i] = temp;
}
}