堆排序原理以及代码解析

前言 堆排序是排序算法的一种,相对来说较难理解,本文分析了堆排序的原理,并且剖析了源码。
堆定义
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。其满足如下公式:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
堆排序基本思想以及步骤

  • 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。
  • 将其与末尾元素进行交换,此时末尾就为最大值。
  • 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。
  • 如此反复执行,便能得到一个有序序列了
排序算法最复杂的是将数组构造成大顶堆。
堆排序源码分析 整体代码
@Override publicvoid sortData(int[] arr) { // 1.构建大顶堆 for (int i = arr.length / 2 - 1; i >= 0; i--) { // 从第一个非叶子结点从下至上,从右至左调整结构 adjustHeap(arr, i, arr.length); } // 2.调整堆结构+交换堆顶元素与末尾元素 for (int j = arr.length - 1; j > 0; j--) { swap(arr, 0, j); // 将堆顶元素与末尾元素进行交换 adjustHeap(arr, 0, j); // 重新对堆进行调整 }}/** * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上) * * @param arr * @param i * @param length */ public static void adjustHeap(int[] arr, int i, int length) { int temp = arr[i]; // 先取出当前元素i for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {// 从i结点的左子结点开始,也就是2i+1处开始 if (k + 1 < length && arr[k] < arr[k + 1]) {// 如果左子结点小于右子结点,k指向右子结点 k++; } if (arr[k] > temp) {// 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换) arr[i] = arr[k]; i = k; } else { break; } } arr[i] = temp; // 将temp值放到最终的位置 }/** * 交换元素 * * @param arr * @param a * @param b */ public static void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }

构造大顶堆
// 1.构建大顶堆 for (int i = arr.length / 2 - 1; i >= 0; i--) { // 从第一个非叶子结点从下至上,从右至左调整结构 adjustHeap(arr, i, arr.length); }

  • 将无序堆构造成大顶堆的原理为:先将最底层的树调整成大顶堆,再将最底层的上一层的树调整成大顶堆,一层接着一层,最后整个树是大顶堆。整个过程是从下到上,从右到左的,每次对平衡树调整时树的左树和右树均已调整成大顶堆。
  • adjustHeap实现的是如果以i为根节点的左树和右树是大顶堆,则将以i为根节点的树调整成大顶堆。
  • 将数组构造成无序平衡二叉树,则0到arr.length/2-1的节点均有左树或者右树,arr.length/2-1之后的节点没有左右树。所以从arr.length/2-1到0进行遍历调整,实现了从右到左,从下到上调整子树。
大顶堆基本调整分析
/** * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上) * * @param arr * @param i * @param length */ public static void adjustHeap(int[] arr, int i, int length) { int temp = arr[i]; // 先取出当前元素i for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {// 从i结点的左子结点开始,也就是2i+1处开始 if (k + 1 < length && arr[k] < arr[k + 1]) {// 如果左子结点小于右子结点,k指向右子结点 k++; } if (arr[k] > temp) {// 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换) arr[i] = arr[k]; i = k; } else { break; } } arr[i] = temp; // 将temp值放到最终的位置 }

adjustHeap将以i为节点的平衡二叉树调整成大顶堆的前提是此平衡二叉树的左树和右树均为大顶堆,此调整是从上到下调整的。调整过程为:
  • 步骤一 调整根节点和左右树的根节点
  • 步骤二 如果没有影响到左右树则调整成功,如果影响到了某个树(左树或者右树),则回到步骤一,调整影响的这颗树。
交互数据元素+调整堆结构分析
for (int j = arr.length - 1; j > 0; j--) { swap(arr, 0, j); // 将堆顶元素与末尾元素进行交换 adjustHeap(arr, 0, j); // 重新对堆进行调整 }

  • 每次交互数据元素后,调整的是根节点,以及第j个节点。此时以0到j-1个节点为树的根节点的左树和右树仍然还是大堆顶,满足adjustHeap的调整情况。
  • 经过调整后仍然变成大堆顶,又可以交互元素,取出最大值。
  • 以此类推最后数组按照升序排列。
联系我 【堆排序原理以及代码解析】邮箱:sysuzhuminh@gmail.com
微信:zhminh
github地址:https://github.com/huolizhuminh/AndroidSkills/blob/master/JavaSkillDemo/src/minhui/demo/sort/HeapSortDemo.java

    推荐阅读