堆的基本介绍
- 堆是完全二叉树:
-
- 每个节点的值都大于或等于其左右孩子的值,称为大顶堆(注意:没有要求节点的左孩子的值和右孩子的值的大小关系)
- 每个节点的值都小于或等于其左右孩子结点的值,称为小顶堆
- 顺序化存储二叉树(大顶堆)特点:
-
- 大于等于左孩子的值:arr[i]>=arr[2*i+1]
- 大于等于右孩子的值:arr[i]>=arr[2*i+2]
- 一般升序采用大顶堆,降序采用小顶堆
堆排序基本思想
1)将待排序序列构造成一个大顶堆
//从最后一个非叶子节点开始构建大顶堆,
// 当运行到非叶子节点的父节点时候,其子节点已经是大顶堆了
// 符合myAdjustHeap的前提条件
for (int i = arr.length / 2 - 1; i >= 0; i--) {
myAdjustHeap(arr, i, arr.length);
}
2)此时,整个序列的最大值就是堆顶的根节点
3)将其与末尾元素进行交换,此时末尾就为最大值
//将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
4)将剩余元素重新构造成一个堆。
// 除了堆顶元素,其他节点已经是大顶堆了
adjustHeap(arr, 0, j);
5)重复2-4步,直到完成排序
for (int j = arr.length - 1; j > 0; j--) {
//将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
// 除了堆顶元素,其他节点已经是大顶堆了
adjustHeap(arr, 0, j);
}
堆排序代码实现
public static void myHeapSort(int[] arr) {
int temp = 0;
//从最后一个非叶子节点开始构建大顶堆,
// 当运行到非叶子节点的父节点时候,其子节点已经是大顶堆了
// 符合myArraySort的前提条件
for (int i = arr.length / 2 - 1; i >= 0; i--) {
myAdjustHeap(arr, i, arr.length);
}
for (int j = arr.length - 1; j > 0; j--) {
//将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
// 除了堆顶元素,其他节点已经是大顶堆了
adjustHeap(arr, 0, j);
}
}
/**
* 前提:除了 topIndex 节点外,topIndex节点以下的节点已经是大顶堆
* 功能:完成 将 以 topIndex 对应的非叶子结点的树调整成大顶堆
* @param arr 除了 topIndex 节点外,topIndex节点以下的节点已经是大顶堆
* @param topIndex topIndex节点的下标
* @param length 剩余代调整的元素的个数,元素下标为:[0,length-1]
*/
public static void myAdjustHeap(int[] arr, int topIndex, int length) {
//先取出当前元素的值,保存在临时变量
int temp = arr[topIndex];
int index = topIndex;
//1. k = i * 2 + 1 k 是 index 节点的左子结点
for (int k = index * 2 + 1; k < length; k = k * 2 + 1) {
// 满足 k+1 < length ,则说明有右子结点
// 满足 arr[k] < arr[k+1] ,则说明右子结点的值更大
// 左右子节点的大值的下标为k
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++; // k 指向右子节点
}
// 左右子节点的大值与temp比较
// 如果子节点大于temp
if (arr[k] > temp) {
//把较大的值赋给当前节点
arr[index] = arr[k];
index = k; //!!! index 指向 k,继续循环比较
} else {
break;
}
}
//当for 循环结束后,我们已经将以i 为父节点的树的最大值,放在了 最顶(局部)
arr[index] = temp;//将temp值放到调整后的位置
}
堆排序、选择和冒泡排序比较分析
冒泡和选择排序,每次得到一个最值,但是每次得到最值的过程,都需要和剩余元素比较,所以时间复杂度为O(n2)。
堆排序首先构建一个规则,然后,后序每次得到一个最值,但是每次得到最值的过程,只需要逐级向下与发生过变动的子堆比较,而且只要不发生交换就可以退出完成堆的构建,首先减少了比较的次数,然后还自然像冒泡一样优化,当然冒泡是不交换就完成了排序过程,在某个节点下已经都是堆的情况下,构建堆是只要出现不交换,就完成了堆的构建,可以在堆顶得到最值。