【数据结构】核心数据结构之二叉堆的原理及实现

简介: 【数据结构】核心数据结构之二叉堆的原理及实现

1.大顶堆和小顶堆原理

  • 什么是堆
  • 堆(Heap)是计算机科学中一类特殊的数据结构,通常是一个可以被看作一颗完全二叉树的数组对象。
  • 完全二叉树
  • 只有最下面两层节点的度可以小于2,并且最下层的叶节点集中在靠左连续的边界
  • 只允许最后一层有空缺结点且空缺在右边,完全二叉树需保证最后一个节点之前的节点都齐全;
  • 对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1


e12363d3a8604f98860d0f759449c1a5.jpg

  • 什么是大顶堆(最大堆)
  • 大顶堆是一种完全二叉树,其每个父节点的值都大于或等于其子节点的值,即根节点的值最大。
  • 每个节点的两个子节点顺序没做要求,和之前的二叉查找树不一样。

6e332ad782b644988337cd5130668c0a.jpg

  • 什么是小顶堆(最小堆)
  • 小顶堆是一种完全二叉树,其每个父节点的值都小于或等于其子节点的值,即根节点的值最小。
  • 每个节点的两个子节点顺序没做要求,和之前的二叉查找树不一样

819edfc183194a4d8d9842d9f4532786.jpg

存储原理

  • 一般升序采用大顶堆,降序采用小顶堆。
  • 堆是一种非线性结构,用数组来存储完全二叉树是非常节省空间的,把堆看作一个数组。
  • 方便操作,一般数组的下标0不存储,直接从1节点存储。
  • 堆其实就是利用完全二叉树的结构来维护一个数组
  • 数据下表为k的节点
  • 左子节点下标为2*k的节点。
  • 右子节点就是下表为2*k+1的节点。
  • 父节点就是下标为k/2取证的节点。
  • 公式描述一下堆的定义
  • 大顶堆:arr[k] >= arr[2k+1] && arr[k] >= arr[2k]
  • 小顶堆:arr[k] <= arr[2k+1] && arr[k] <=arr[ak]
  • 小顶堆动画效果演示
  • 往堆中插入新元素,就是往数组中从索引0或1开始依次存放数据,但是顺序需要满足堆的特性
  • 如何让堆满足:
  • 不断比较新节点 arr[k]和对应父节点arr[k/2]的大小,根据情况交互元素位置
  • 直到找到的父节点比当前新增节点大则结束

5bff144eff6d4a39ad3b79ffd29fa669.gif

2.大顶堆构编码实现

  • 大顶堆(最大堆)
  • 大顶堆是一种完全二叉树,其每个父节点的值都大于或等于其子节点的值,即根节点的值最大


0458b092b5f542cc804b15a3caa49954.jpg

  • 编码实现
public class Heap {
    //用数组存储堆中的元素
    private int[] items;
    //堆中元素的个数
    private int num;
    public Heap(int capacity) {
        //数组下标0不存储数据,所以容量+1
        this.items = new int[capacity + 1];
        this.num = 0;
    }
    /**
     * 判断堆中 items[left] 元素是否小于 items[right] 的元素
     */
    private boolean rightBig(int left, int right) {
        return items[left] < items[right];
    }
    /**
     * 交换堆中的两个元素位置
     */
    private void swap(int i, int j) {
        int temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }
    /**
     * 往堆中插入一个元素,默认是最后面,++num先执行,然后进行上浮判断操作
     */
    public void insert(int value) {
        items[++num] = value;
        up(num);
    }
    /**
     * 使用上浮操作,新增元素后,重新堆化
     * 不断比较新节点 arr[k]和对应父节点arr[k/2]的大小,根据情况交互元素位置
     * 直到找到的父节点比当前新增节点大则结束
     * <p>
     * 数组中下标为 k 的节点
     * 左子节点下标为 2*k 的节点
     * 右子节点就是下标 为 2*k+1 的节点
     * 父节点就是下标为 k/2 取整的节点
     */
    private void up(int k) {
        //父节点 在数组的下标是1,下标大于1都要比较
        while (k > 1) {
            //比较 父结点 和 当前结点 大小
            if (rightBig(k / 2, k)) {
                //当前节点大,则和父节点交互位置
                swap(k / 2, k);
            }
            // 往上一层比较,当前节点变为父节点
            k = k / 2;
        }
    }
    /**
     * 删除堆中最大的元素,返回这个最大元素
     */
    public int delMax() {
        int max = items[1];
        //交换索引 堆顶的元素(数组索引1的)和 最大索引处的元素,放到完全二叉树中最右侧的元素,方便后续变为临时根结点
        // 为啥不能直接删除顶部元素,因为删除后会断裂,成为森林,所以需要先交互,再删除
        swap(1, num);
        //最大索引处的元素删除掉, num--是后执行,元素个数需要减少1
        items[num--] = 0;
        //通过下浮调整堆,重新堆化
        down(1);
        return max;
    }
    /**
     * 使用下沉操作,堆顶和最后一个元素交换后,重新堆化
     * 不断比较 节点 arr[k]和对应 左节点arr[2*k] 和 右节点arr[2*k+1]的大小,如果当前结点小,则需要交换位置
     * 直到找到 最后一个索引节点比较完成  则结束
     * 数组中下标为 k 的节点
     * 左子节点下标为 2*k 的节点
     * 右子节点就是下标 为 2*k+1 的节点
     * 父节点就是下标为 k/2 取整的节点
     */
    private void down(int k) {
        //最后一个节点下标是num
        while (2 * k <= num) {
            //记录当前结点的左右子结点中,较大的结点
            int maxIndex;
            if (2 * k + 1 <= num) { //2 * k + 1 <= num 是判断 确保有右节点
                //比较当前结点下的左右子节点哪个大
                if (rightBig(2 * k, 2 * k + 1)) {
                    maxIndex = 2 * k + 1;
                } else {
                    maxIndex = 2 * k;
                }
            } else {
                maxIndex = 2 * k;
            }
            //比较当前结点 和 较大结点的值, 如果当前节点较大则结束
            if (items[k] > items[maxIndex]) {
                break;
            } else {
                //否则往下一层比较,当前节点k索引 变换为 子节点中较大的值
                swap(k, maxIndex);
                //变换k的值
                k = maxIndex;
            }
        }
    }
    public static void main(String[] args) {
        Heap heap = new Heap(20);
        heap.insert(42);
        heap.insert(48);
        heap.insert(93);
        heap.insert(21);
        heap.insert(90);
        heap.insert(9);
        heap.insert(3);
        heap.insert(40);
        heap.insert(32);
        int top;
        System.out.println("输出堆:");
        while ((top = heap.delMax()) != 0) {
            System.out.print(top + " ");
        }
    }
}


837b000462f347299887826f480bf46f.jpg

相关文章
|
2月前
|
存储 编译器 数据库
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
58 0
|
2月前
|
算法 调度 C++
【C/C++ 数据结构 线性表】C/C++中队列的原理与实现:从基础到循环队列
【C/C++ 数据结构 线性表】C/C++中队列的原理与实现:从基础到循环队列
44 0
|
4月前
|
存储 NoSQL 安全
Redis相关命令详解及其原理:Redis基本操作、数据结构以及应用
Redis相关命令详解及其原理:Redis基本操作、数据结构以及应用
98 0
|
9天前
|
存储 算法 Java
22个常用数据结构实现与原理分析
前两天V哥跟一个老学员吃饭,聊起面试大厂的事,说为啥大厂面试第一看基本条件,第二就是考数据结构算法,其他高阶的内容会比较少,最近V哥也在跟大厂对接这一块业务,了解得多一些,这是因为考察基本功能力被放到了重要位置,大厂认为硬性条件,比如学历过关,基本功够扎实,那对于实际工作用的上层技能,内部培养就好,也就是相比你掌握了多少多少牛逼的高阶技术,他们更在乎你的基本功,所以,进大厂,基本功必须要搞稳,否则白扯,今天 V 哥把总结好的22个常用的数据结构实现原理,和示例分析分享给大家,希望对你有帮助,觉得内容有收获,请帮忙转发给更多需求的朋友,共同进步。
|
1月前
|
算法 调度 C++
[数据结构与算法]贪心算法(原理+代码)
[数据结构与算法]贪心算法(原理+代码)
|
1月前
|
存储 算法 编译器
【数据结构】栈算法(算法原理+源码)
【数据结构】栈算法(算法原理+源码)
【数据结构】栈算法(算法原理+源码)
|
1月前
|
算法 搜索推荐 数据库
【数据结构】二叉树算法讲解(定义+算法原理+源码)
【数据结构】二叉树算法讲解(定义+算法原理+源码)
|
1月前
|
缓存 算法 搜索推荐
【数据结构】链表(单链表与双链表实现+原理+源码)
【数据结构】链表(单链表与双链表实现+原理+源码)
|
2月前
|
存储 NoSQL Redis
作者推荐 |【Redis技术进阶之路】「原理系列开篇」揭秘高效存储模型与数据结构底层实现(SDS)(三)
作者推荐 |【Redis技术进阶之路】「原理系列开篇」揭秘高效存储模型与数据结构底层实现(SDS)
39 0
|
2月前
|
存储 NoSQL 安全
作者推荐 |【Redis技术进阶之路】「原理系列开篇」揭秘高效存储模型与数据结构底层实现(SDS)(二)
作者推荐 |【Redis技术进阶之路】「原理系列开篇」揭秘高效存储模型与数据结构底层实现(SDS)
26 0