堆排序算法介绍
堆是一种重要的数据结构,为一棵完全二叉树, 底层如果用数组存储数据的话,假设某个元素为序号为i(Java数组从0开始,i为0到n-1),如果它有左子树,那么左子树的位置是2i+1,如果有右子树,右子树的位置是2i+2,如果有父节点,父节点的位置是(i-1)/2取整。分为最大堆和最小堆,最大堆的根结点不小于任意子结点,最小堆的根结点不大于任意子结点。任意子树也满足堆的特征!所谓堆排序就是利用堆这种数据结构来对数组排序。处理的思想和冒泡排序,选择排序非常的类似,一层层封顶,只是最大元素的选取使用了最大堆。
堆排序思想
给定一个数组,可以看做是一个完全二叉树(层次遍历的结果)。
完全二叉树最后一个非叶结点在(length - 1)/2处。
- 建堆:从下而上(从数组最后一个元素开始遍历)向上调整堆(最大堆)。
- 排序:将堆顶元素(索引为0的数组元素)与数组最后一个元素互换,然后再从根结点向下调整堆,循环往复。(这样,数组从尾部开始递减排序。)
代码实现
package sort;
public class HeapSort {
public static void heapSort(int[] data) {
if (data == null || data.length <= 0) {
throw new IllegalArgumentException("Invalid parameters in partition method!");
}
int length = data.length;
//建立堆
for (int index = (length - 1); index > 0; index--) {
siftUp(index, data); //向上调整堆
}
int tmp;
for (int i = 0; i < (data.length - 1); i++) {
tmp = data[0];
data[0] = data[length - 1 - i];
data[length - 1 - i] = tmp;
siftDown(0, data, length - 1 - i); //向下调整堆
}
}
/**
* 向上调整堆
*
* @param index 被上移元素的起始位置
* @param items
*/
public static void siftUp(int index, int[] items) {
int intent = items[index]; // 获取开始调整的元素对象
while (index > 0) { // 如果不是根元素
int parentIndex = (index - 1) / 2; // 找父元素对象的位置
int parent = items[parentIndex]; // 获取父元素对象
if (intent > parent) { //上移的条件,子节点比父节点大
items[index] = parent; // 将父节点向下放
index = parentIndex; // 记录父节点下放的位置
} else { // 子节点不比父节点大,说明父子路径已经按从大到小排好顺序了,不需要调整了
break;
}
}
// index此时记录是的最后一个被下放的父节点的位置(也可能是自身),所以将最开始的调整的元素值放入index位置即可
items[index] = intent;
}
/**
* 向下调整堆
*
* @param index 被下移的元素的起始位置
*/
/**
* 向下调整堆
*
* @param index 被下移的元素的起始位置
* @param items
* @param length 数组边界
*/
public static void siftDown(int index, int[] items, int length) {
int intent = items[index]; // 获取开始调整的元素对象
int leftIndex = (index << 1) + 1; // // 获取开始调整的元素对象的左子结点的元素位置
while (leftIndex < length) { // 如果有左子结点;堆是完全二叉树逻辑结构,其中某个结点没有左子结点的话就没有右子结点
int maxChild = items[leftIndex]; // 取左子结点的元素对象,并且假定其为两个子结点中最大的
int maxIndex = leftIndex; // 两个子节点中最大节点元素的位置,假定开始时为左子结点的位置
int rightIndex = leftIndex + 1; // 获取右子结点的位置
if (rightIndex < length) { // 如果有右子结点
int rightChild = items[rightIndex]; // 获取右子结点的元素对象
if (rightChild > maxChild) { // 找出两个子节点中的最大子结点
maxChild = rightChild;
maxIndex = rightIndex;
}
}
// 如果最大子节点比父节点大,则需要向下调整
if (maxChild > intent) {
items[index] = maxChild; // 将子节点向上移
index = maxIndex; // 记录上移节点的位置
leftIndex = (index << 1) + 1; // 找到上移节点的左子节点的位置
} else { // 最大子节点不比父节点大,说明父子路径已经按从大到小排好顺序了,不需要调整了
break;
}
}
// index此时记录是的最后一个被上移的子节点的位置(也可能是自身),所以将最开始的调整的元素值放入index位置即可
items[index] = intent;
}
}
性能分析:不稳定
向上调整堆和向下调整堆的时间复杂度是:O(logn),跟树的高度有关。
所以,堆排序的时间复杂度是:O(nlogn)