堆排序

87 阅读4分钟

堆的基本介绍

  • 堆是完全二叉树:
    • 每个节点的值都大于或等于其左右孩子的值,称为大顶堆(注意:没有要求节点的左孩子的值和右孩子的值的大小关系)
    • 每个节点的值都小于或等于其左右孩子结点的值,称为小顶堆
  • 顺序化存储二叉树(大顶堆)特点:
    • 大于等于左孩子的值: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)。

堆排序首先构建一个规则,然后,后序每次得到一个最值,但是每次得到最值的过程,只需要逐级向下与发生过变动的子堆比较,而且只要不发生交换就可以退出完成堆的构建,首先减少了比较的次数,然后还自然像冒泡一样优化,当然冒泡是不交换就完成了排序过程,在某个节点下已经都是堆的情况下,构建堆是只要出现不交换,就完成了堆的构建,可以在堆顶得到最值。