二叉堆

1.什么是二叉堆?

  二叉堆本质上是一种完全二叉树,它分为两个类型。

  • 最大堆—最大堆的任何一个父节点的值,都大于或等于它左、右孩子节点的值。
  • 最小堆—最小堆的任何一个父节点的值,都小于或等于它左、右孩子节点的值。

  二叉堆的根节点叫做堆顶。

  最大堆和最小堆的特点决定了:最大堆堆顶是整个堆中的最大元素;最小堆的堆顶是整个堆中的最小元素。

2.二叉堆的构建

  二叉堆的构建需要依靠二叉堆的自我调整

  2.1 二叉堆的自我调整

    对于二叉堆,有如下几种操作:

      • 插入节点
      • 删除节点
      • 构建二叉堆

    这几种操作都是基于堆的自我调整。所谓堆的自我调整,就是把一个不符合堆性质的完全二叉树,调整成一个堆。

    2.1.1 插入节点(以最小堆为例)

      当二叉堆插入节点时,插入位置是完全二叉树的最后一个位置。新节点与父节点比较大小,小于父节点,则新节点上浮,与父节点交换位置。依次循环比较,知道最小值到达堆顶位置,并且所有父节点都小于或等于它左、右孩子节点。

    2.1.2 删除节点

      二叉堆删除节点的过程与插入节点的过程正好相反,所删除的是处于堆顶的节点。

      需要注意的是,当删除堆顶的节点后,为了继续维持完全二叉树的结构,我们把堆的最后一个节点临时补到原本堆顶的位置,接下来,让堆顶位置的节点与它的左右孩子节点进行比较,来满足最小堆或者最大堆的特性。

    2.1.3 构建二叉堆

      构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次下沉。

      首先,需要从最后一个非叶子节点开始,与它的左右孩子节点进行比较,并按照最大堆或者最小堆的特性依次完成节点的下沉。 

  2.2 二叉堆的代码实现

    首先,我们要明确一点,二叉堆虽然是一个完全二叉树,但是它的存储方式并不是链式存储,二是顺序存储。换句话说,二叉堆的所有节点都存储在数组中。

    在数组中,在没有左右指针的情况下,如何定位一个父节点的左孩子和右孩子呢?

    假设父节点的下标是parent,那么它左孩子的下标就是2 * parent + 1;右孩子的下标就是2 * parent +2。

    简单的代码实现如下:

 1 package com.algorithm.test;
 2 
 3 import java.util.Arrays;
 4 
 5 /**
 6  * @Author Jack丶WeTa
 7  * @Date 2020/7/30 11:13
 8  * @Description 二叉堆的代码实现
 9  */
10 public class HeapTest {
11 
12     /**
13      * "上浮"调整
14      * @param array 待调整的堆
15      */
16     public static void upAdjust(int[] array){
17         int childIndex = array.length - 1;
18         int parentIndex = (array.length - 1) / 2;
19         //temp保存插入的叶子节点的值,用于最后的赋值
20         int temp = array[childIndex];
21         while (childIndex > 0 && temp < array[parentIndex]) {
22             //无须真正交换,单向赋值即可
23             array[childIndex] = array[parentIndex];
24             childIndex = parentIndex;
25             parentIndex = (parentIndex - 1) / 2;
26         }
27         array[childIndex] = temp;
28     }
29 
30     /**
31      * "下称调整"
32      * @param array         待调整的堆
33      * @param parentIndex   要“下称”的父节点
34      * @param length        堆的有效大小
35      */
36     public static void downAdjust(int[] array, int parentIndex, int length){
37         //temp保存父节点的值,用于最后赋值
38         int temp = array[parentIndex];
39         int childIndex = 2 * parentIndex + 1;
40         while (childIndex < length) {
41             //如果有右孩子,且右孩子小于左孩子的值则定位到右孩子
42             if (childIndex + 1 < length && array[childIndex+1] < array[childIndex])
43                 childIndex++;
44             //如果父节点小于任何一个孩子的值,则直接跳出
45             if (temp < array[childIndex])
46                 break;
47 
48             //无须真正交换,单向赋值即可
49             array[parentIndex] = array[childIndex];
50             parentIndex = childIndex;
51             childIndex = 2 * childIndex + 1;
52         }
53         array[parentIndex] = temp;
54     }
55 
56     /**
57      * 构建堆
58      * @param array 待调整的堆
59      */
60     public static void buildHeap(int[] array){
61         //从最后一个非叶子节点开始,依次做“下沉”调整
62         for (int i = (array.length - 2) / 2; i >= 0; i--){
63             downAdjust(array, i, array.length);
64         }
65     }
66 
67     public static void main(String[] args) {
68         int[] array = new int[]{1,3,2,6,5,7,8,9,10,0};
69         upAdjust(array);
70         System.out.println(Arrays.toString(array));
71 
72         array = new int[]{7,1,3,10,5,2,8,9,6};
73         buildHeap(array);
74         System.out.println(Arrays.toString(array));
75     }
76 }

  堆的插入操作是单一节点的“上浮”,堆的删除操作是单一节点的“下沉”,这两个操作的平均交换次数都是堆高度的一半,所以时间复杂度是0(logn),至于堆的构建,则是O(n)。

原文地址:https://www.cnblogs.com/JackWeTa/p/13402974.html