堆排序【二叉堆简介】【二叉堆插入/删除堆顶/构造】【堆排序】【最大堆/最小堆】

0 - 前言

本文是对下面两篇博文做的笔记

参考:

【算法与数据结构】二叉堆是什么鬼?

【算法与数据结构】堆排序是什么鬼?

1 - 二叉堆简介

二叉堆是一种完全二叉树,分为最大堆和最小堆

  • 最大堆:任意父节点的值 ≥ 左右子节点的值;堆顶(即二叉树的根节点)是堆中最大元素
  • 最小堆:任意父节点的值 ≤ 左右子节点的值;堆顶是堆中最小元素

完全二叉树:叶结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。

参考:百度百科 - 完全二叉树

注意,二叉树以链表形式实现(每个节点都有指向子节点的指针),二叉堆以数组形式实现

1-1 向二叉堆中插入节点(最小堆)

  1. 将插入节点添加到二叉堆最后一个位置,相当于二叉堆节点数组的尾部
  2. 从插入位置开始,将插入节点与父节点比较,如果<父节点(如果是最大堆就要>父节点),那么将插入节点上浮(与父节点交换位置)。上浮直到上浮条件被打破或者到达根节点之时。

1-2 删除堆顶节点(最小堆)

  1. 将堆顶节点删除后,将二叉堆最后位置上的节点提到堆顶(即将二叉堆节点数组的最后一个元素移到数组开头)
  2. 从堆顶开始,如果左右子节点<新父节点(如果是最大堆就要>父节点),那么将新父节点下沉 (与满足下沉条件的子节点交换位置,如果左右子节点都满足下沉条件,将父节点与最小的子节点交换位置)。下沉直到下沉条件被打破或者下沉到叶节点)

1-3 将完全二叉树转化为二叉堆

将一个无序的完全二叉树转化为二叉堆,可以让所有的非叶节点依次下沉(最大堆或最小堆都适用)

注意下沉不是从上到下(堆顶到叶节点),而是从下到上(从最下层的非叶子节点开始下沉)

1-4 代码实现

因为二叉堆使用数组实现,所以要知道父节点与子节点的下标关系:

一个节点的下标为n,它的左子节点下标为2n+1,右子节点下标为2n+2,父节点坐标为(n-1)/2,注意是整除

public class BinaryHeap {
    
    

   /**上浮操作,对插入的节点进行上浮
    *
    * @param arr
    * @param length :表示二叉堆的长度
    */
   public static int[] upAdjust(int arr[], int length){
    
    
       //标记插入的节点
       int child = length - 1;
       //其父亲节点
       int parent = (child - 1)/2;
       //把插入的节点临时保存起来
       int temp = arr[child];

       //进行上浮
       while (child > 0 && temp < arr[parent]) {
    
    
           //其实不用进行每次都进行交换,单向赋值就可以了
           //当temp找到正确的位置之后,我们再把temp的值赋给这个节点
           arr[child] = arr[parent];
           child = parent;
           parent = (child - 1) / 2;
       }
       //退出循环代表找到正确的位置
       arr[child] = temp;
       return arr;
   }

   /**

    */

   /**
    *  下沉操作,执行删除操作相当于把最后
    *  * 一个元素赋给根元素之后,然后对根元素执行下沉操作
    * @param arr
    * @param parent 要下沉元素的下标
    * @param length 数组长度
    */
   public static int[] downAdjust(int[] arr, int parent, int length) {
    
    
       //临时保证要下沉的元素
       int temp = arr[parent];
       //定位左孩子节点位置
       int child = 2 * parent + 1;
       //开始下沉
       while (child < length) {
    
    
           //如果右孩子节点比左孩子小,则定位到右孩子
           if (child + 1 < length && arr[child] > arr[child + 1]) {
    
    
               child++;
           }
           //如果父节点比孩子节点小或等于,则下沉结束
           if (temp <= arr[child])
               break;
           //单向赋值
           arr[parent] = arr[child];
           parent = child;
           child = 2 * parent + 1;
       }
       arr[parent] = temp;
       return arr;
   }

   /**
    * 构建操作
    *
    * @param arr
    */
   public static int[] buildHead(int[] arr,int length) {
    
    
       //从最后一个非叶子节点开始下沉
       for (int i = (length - 2) / 2; i >= 0; i--) {
    
    
           arr = downAdjust(arr, i, length);
       }
       return arr;
   }
}

代码注释很清楚了,解释一下为什么构建二叉堆的时候从i = (length - 2) / 2开始,这是因为最后一个节点下表为length - 1,该节点对应的父节点是最后一个非叶节点,根据父节点坐标为(n-1)/2得到最后一个非叶节点的下标就是(length - 1 - 1) / 2

2 - 堆排序

给定一个无序数组,首先将该数组构建为二叉堆,然后采用逐步删除堆顶节点、将删除节点存放于二叉堆最后位置上的方式实现堆排序(因为删除堆顶节点会将二叉堆最后位置上的节点提到堆顶并下沉)。

简言之,就是交换堆顶与堆最后位置的节点,并对交换以后的堆下沉,保证下一次交换还是一个二叉堆。等到原本堆中最后一个节点交换到堆顶时,二叉堆已经排序完毕(即二叉堆数组满足降序或者升序)

public class HeapSort {
    
    
   /**
    *  下沉操作,执行删除操作相当于把最后
    *  * 一个元素赋给根元素之后,然后对根元素执行下沉操作
    * @param arr
    * @param parent 要下沉元素的下标
    * @param length 数组长度
    */
   public static int[] downAdjust(int[] arr, int parent, int length) {
    
    
       //临时保证要下沉的元素
       int temp = arr[parent];
       //定位左孩子节点位置
       int child = 2 * parent + 1;
       //开始下沉
       while (child < length) {
    
    
           //如果右孩子节点比左孩子小,则定位到右孩子
           if (child + 1 < length && arr[child] > arr[child + 1]) {
    
    
               child++;
           }
           //如果父节点比孩子节点小或等于,则下沉结束
           if (temp <= arr[child])
               break;
           //单向赋值
           arr[parent] = arr[child];
           parent = child;
           child = 2 * parent + 1;
       }
       arr[parent] = temp;
       return arr;
   }

   //堆排序
   public static int[] heapSort(int[] arr, int length) {
    
    
       //构建二叉堆
       for (int i = (length - 2) / 2; i >= 0; i--) {
    
    
           arr = downAdjust(arr, i, length);
       }
       //进行堆排序
       for (int i = length - 1; i >= 1; i--) {
    
    
           //把堆顶的元素与最后一个元素交换
           int temp = arr[i];
           arr[i] = arr[0];
           arr[0] = temp;
           //下沉调整
           arr = downAdjust(arr, 0, i);
       }
       return arr;
   }
   //测试
   public static void main(String[] args) {
    
    
       int[] arr = new int[]{
    
    1, 3, 5,2, 0,10,6};
       System.out.println(Arrays.toString(arr));
       arr = heapSort(arr, arr.length);
       System.out.println(Arrays.toString(arr));
   }
}

猜你喜欢

转载自blog.csdn.net/weixin_44484715/article/details/117113393
今日推荐