动画:一篇文章快速学会数据结构-堆

内容介绍

树结构简介

树结构是计算机中经常使用的一种数据结构。咱们先来看一下生活中的树: java

计算机中的树和生活中的树是相似的,只不过是倒着的,树根在上,树叶在下。树上的每一个组成元素都是一个节点,树根称为根节点,树枝称为分支节点,树叶称为叶子节点,以下图所示: 编程

二叉树结构简介

二叉树是:每一个节点最多只能有两个子节点树。二叉树的子节点分为左节点和右节点,以下图:api

满二叉树:若是该二叉树的全部叶子节点都在最后一层,而且节点总数= 2^n -1 , n 为层数,则咱们称为满二叉树,以下图: 数组

彻底二叉树:若是该二叉树的全部叶子节点都在最后一层或者倒数第二层,并且最后一层的叶子节点在左边连续,倒数第二 层的叶子节点在右边连续,咱们称为彻底二叉树,以下图: 微信

咱们这里注意理解彻底二叉树,由于堆结构是一种特殊的彻底二叉树。关于树的结构,咱们先简单介绍,之后会专门讲解树结构,咱们这里主要是讲堆结构,因此先简单说起一下树结构。数据结构

堆结构简介

堆(Heap)是一种特殊的树形数据结构,每一个结点都有一个值。常见的堆有二叉堆、斐波那契堆等,一般咱们所说的堆的数据结构,是指二叉堆。以下图: 动画

堆知足下列两个性质:code

  1. 堆中某个节点的值老是不大于或不小于其父节点的值。
  2. 堆老是一棵彻底二叉树。 根节点最大的堆叫作最大堆大根堆,根节点最小的堆叫作最小堆小根堆,以下图所示:

堆的存储

堆是非线性数据结构,能够使用一维数组来存储,将堆中序号对应的数据放到数组对应的索引中,以下图: blog

堆的一些概念和规律

概念:排序

  1. 某节点左边的子节点成为:左孩子。
  2. 某节点右边的子节点成为:右孩子。
  3. 某节点的上一个节点成为:父节点。

规律:假设当前节点的索引为i

  1. 父节点索引 = (i - 1) / 2 (Java中除以2取整数,好比7/2 = 3)
  2. 左孩子索引 = 2 * i + 1
  3. 右孩子索引 = 2 * i + 2

堆的定义性质:

  1. 最大堆节点的值大于左右孩子的值,也就是知足:arr[i] > arr[2*i+1] && arr[i] > arr[2*i+2],以下图:

堆获取最大值

获取最大堆的最大值,其实就是获取堆中最前面一个元素。对于堆这种数据结构一般是将最前面的元素和最后面的元素换位置,最大值就到了最后一个位置,而后从堆中排除这个元素,当最后一个元素交换到最前面时,此时就不知足堆的性质了,咱们须要将最前面这个元素经过ShiftDown(下沉)的手段让堆继续知足堆的规则。

堆获取最大值能够分红两个步骤:

  1. 将堆中最前面的最大值和最后一个元素交换位置。
  2. 使用ShiftDown让最前面的元素下沉到合适的位置,依然知足堆的性质。 动画演示效果以下:

这里面重点注意,ShiftDown可让堆中的一个元素下沉到合适的位置,而且知足堆的规则。后面咱们构建堆就须要使用到ShiftDown操做。

ShiftDown详细图解:

最大堆的最后一个非叶子节点

  1. 咱们构建堆时须要从最后一个非叶子节点开始按照规则构建堆,因此咱们须要知道最后一个非叶子节点计算公式:(堆的最大索引-1) / 2。

构建一个堆结构

构建堆实际上是将无序的彻底二叉树调整为二叉堆。非叶子节点没有子节点不须要从新构建,而后自底向上对每个子树执行SiftDown操做,直到完成二叉堆化。 假设咱们如今有一个数组,内容为:{6, 3, 7, 5, 8, 2, 1, 4, 9},它是不知足堆的规则,咱们如今将这个数组构建成一个二叉堆,步骤为:

  1. 找到最后一个非叶子节点,使用ShiftDown下沉,使这个颗树知足堆的规则。
  2. 找到倒数第二个非叶子节点,使用ShiftDown下沉,使这个颗树知足堆的规则。
  3. 以此类推,直到找到最前面的一个元素使用ShiftDown下沉,使这颗树知足堆的规则。 将无序的彻底二叉树调整为二叉堆的过程称为heapify,动画以下:

堆添加数据

往堆中插入一个元素,是在数组的最末尾插入新的数据,此时可能不知足堆的特性,咱们须要进行自下而上调整子节点和父节点,不知足堆性质则交换父子元素,直到当前子树知足堆的性质。动画效果以下:

代码以下:

public class Heap {
    public static void main(String[] args) {
        int[] arr = {6, 3, 7, 5, 8, 2, 1, 4, 9};
        heapify(arr);

        System.out.println("构建堆后:" + Arrays.toString(arr));
        arr = insert(arr, 11);
        System.out.println("堆中插入数据后:" + Arrays.toString(arr));
    }

    // 往数组中添加一个数据
    public static int[] insert(int[] arr, int element) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        // 复制以前的数组数据到新数组中
        arr[arr.length-1] = element;
        
        shiftUp(arr, arr.length-1);
        return arr;
    }

    // 上浮操做,将i索引元素上浮到合适位置,保证知足堆的两个特性
    private static void shiftUp(int[] arr, int i) {
        // (i-1) / 2: 是i的父节点
        while ((i-1) / 2 >= 0 && arr[(i-1) / 2] < arr[i]) {
            swap(arr, (i-1) / 2, i);
            i = (i-1) / 2;
        }
    }

    // heapify将无序的彻底二叉树调整为二叉堆
    private static void heapify(int[] arr) {
        // 从非叶子节点开始,Shift Down将每一个子树构建成最大堆
        for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(arr, i);
        }
    }

    // 下沉操做,将指定元素下沉到子树的合适位置,使这个颗树知足堆的规则。
    private static void shiftDown(int[] arr, int i) {
        // 循环找子孩子交换位置。左孩子不能越界
        while (2*i + 1 < arr.length) {
            // 假设要交换的是左孩子
            int j = 2*i + 1;
            // 判断是否有有孩子,而且右孩子是否大于左孩子
            if (j+1 < arr.length && arr[j+1] > arr[j]) {
                j++; // 若是是,和右孩子交换
            }

            // 若是当前节点大于两个孩子,就不须要交换
            if (arr[i] > arr[j])
                break;

            // 当前节点小于子孩子,将当前节点和较大的子孩子交换
            swap(arr, i, j);
            // 在判断下一层
            i = j;
        }
    }

    public static void swap(int[] arr, int start, int end) {
        if (start == end) return;

        int temp = arr[start];
        arr[start] = arr[end];
        arr[end] = temp;
    }
}

添加元素放在堆的最后面,使用ShiftUp让元素上浮,让添加的元素找到一个合适的位置,让堆中的数据依然知足堆的特性。

堆的做用

  1. 用做优先队列,咱们知道堆的最前一个元素就是堆的最大值。咱们能够依据优先级来构建堆,每次都取出堆中优先级最高的那个数据。
  2. 用做堆排序。
  3. 查找第N大(小)元素。
  4. 查找前N大(小)元素。

后续咱们会选择合适的时间来完成上面这些功能。

总结

  1. 堆是一种特殊的彻底二叉树,堆中某个节点的值老是不大于或不小于其父节点的值。堆是非线性数据结构,使用数组来存储,操做堆其实就是操做数组中的数据。
  2. 假设当前节点的索引为i,父节点索引 = (i - 1) / 2 (Java中除以2取整数,好比7/2 = 3),左孩子索引 = 2 * i + 1,右孩子索引 = 2 * i + 2
  3. 使用ShiftDown让堆中最前面的元素下沉到合适的位置,让堆依然知足堆的性质。
  4. 添加元素放在堆的最后面,使用ShiftUp让元素上浮,让添加的元素找到一个合适的位置,让堆中的数据依然知足堆的特性。

原创文章和动画制做真心不易,您的点赞就是最大的支持! 想了解更多文章请关注微信公众号:表哥动画学编程

相关文章
相关标签/搜索