首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【堆内存】动态图+代码来轻松理解!!!

【堆内存】动态图+代码来轻松理解!!!

作者头像
用户5224393
发布于 2019-08-13 08:10:15
发布于 2019-08-13 08:10:15
68600
代码可运行
举报
文章被收录于专栏:Java研发军团Java研发军团
运行总次数:0
代码可运行

堆(heap)又被为优先队列(priority queue)。尽管名为优先队列,但堆并不是队列。 因为队列中允许的操作是先进先出(FIFO),在队尾插入元素,在队头取出元素。 而堆虽然在堆底插入元素,在堆顶取出元素,但是堆中元素的排列不是按照到来的先后顺序,而是按照一定的优先顺序排列的。

本文通过堆的实现、最小堆(最大堆)、堆的时间复杂度、优先队列的实现、堆排序来介绍「 堆 」。

堆的实现

堆的一个经典的实现是完全二叉树(complete binary tree),这样实现的堆称为二叉堆(binary heap)。

这里来说明一下满二叉树的概念与完全二叉树的概念。

满二叉树:除了叶子节点,所有的节点的左右孩子都不为空,就是一棵满二叉树,如下图。

可以看出:满二叉树所有的节点都拥有左孩子,又拥有右孩子。

完全二叉树:不一定是一个满二叉树,但它不满的那部分一定在右下侧,如下图

堆的特性:

  • 必须是完全二叉树
  • 任一结点的值是其子树所有结点的最大值或最小值

  • 最大值时,称为“最大堆”,也称大顶堆;
  • 最小值时,称为“最小堆”,也称小顶堆。
堆的基础实现

只要谨记堆的定义特性,实现起来其实是很容易的。

  • 特性1. 维持完全二叉树
  • 特性2. 子类数字总是大于父类数字
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MinHeap <E extends Comparable<E>> {
    private Array<E> data;

    public MinHeap(int capacity){
        data = new Array<>(capacity);
    }

    public MinHeap(){
        data = new Array<>();
    }

    // 返回堆中的元素个数
    public int size(){
        return data.getSize();
    }

    // 返回一个布尔值, 表示堆中是否为空
    public boolean isEmpty(){
        return data.isEmpty();
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
    private int parent(int index){
        return (index - 1) / 2;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
    private int leftChild(int index){
        return index * 2 + 1;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
    private int rightChild(int index){
        return index * 2 + 2;
    }
}

最小堆的插入(ADD)

假设现有元素 5 需要插入,为了维持完全二叉树的特性,新插入的元素一定是放在结点 6 的右子树;同时为了满足任一结点的值要小于左右子树的值这一特性,新插入的元素要和其父结点作比较,如果比父结点小,就要把父结点拉下来顶替当前结点的位置,自己则依次不断向上寻找,找到比自己大的父结点就拉下来,直到没有符合条件的值为止。

动画讲解:

  1. 在这里先将元素 5 插入到末尾,即放在结点 6 的右子树。
  2. 然后与父类比较, 6 > 5 ,父类数字大于子类数字,子类与父类交换。
  3. 重复此操作,直到不发生替换。

Show me the code:

添加一个辅助函数,用来交换传入的索引两个位置的元素值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
     * 交换传入的索引两个位置的元素值
     *
     * @param i
     * @param j
     */
    public void swap(int i, int j) {
        if (i < 0 || i >= size || j < 0 || j >= size)
            throw new IllegalArgumentException("Index is illegal.");

        E temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

数组中添加交换两元素位置的方法,注意下面代码中注释的描述特性位置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * 堆中添加元素方法。
     *
     * @param e
     */
    public void add(E e) {
        //特性1:新插入的元素首先放在数组最后,保持完全二叉树的特性
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    /**
     * index 为i位置元素上浮。
     *
     * @param i
     */
    private void siftUp(int i) {
         //特性2:比较插入值和其父结点的大小关系,小于父结点则用父结点替换当前值,index位置上升为父结点
        // 当上浮元素大于父亲,继续上浮。并且不能上浮到0之上
        // 直到i 等于 0 或 比 父亲节点小了。
        while (i > 0 && data.get(i).compareTo(data.get(parent(i))) > 0) {
            // 数组Array中添加方法swap
            data.swap(i, parent(i));
            i = parent(i); // 这句话让i来到新的位置,使得循环可以查看新的位置是否还要大。
        }
    }

最小堆的删除(DELETE)

核心点:将最后一个元素填充到堆顶,然后不断的下沉这个元素。

假设要从节点 1 ,也可以称为取出节点 1 ,为了维持完全二叉树的特性 ,我们将最后一个元素 6 去替代这个 1 ;然后比较 1 和其子树的大小关系,如果比左右子树大(如果存在的话),就要从左右子树中找一个较小的值替换它,而它能自己就要跑到对应子树的位置,再次循环这种操作,直到没有子树比它小。

通过这样的操作,堆依然是堆,总结一下:

  • 找到要删除的节点(取出的节点)在数组中的位置
  • 用数组中最后一个元素替代这个位置的元素
  • 当前位置和其左右子树比较,保证符合最小堆的节点间规则
  • 删除最后一个元素

Show me the code:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public E findMin() {
        return data.get(0);
    }

    public E extractMin() {

        E ret = findMin();

        data.swap(0, data.getSize() - 1); // 0位置元素和最后一个元素互换。
        data.removeLast(); // 删除此时的最后一个元素(最小值)
        siftDown(0); // 对于0处进行siftDown操作

        return ret;
    }

    /**
     * k位置元素下移
     *
     * @param k
     */
    private void siftDown(int k) {

         while(leftChild(k) < data.getSize()){
            int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置
            if( j + 1 < data.getSize() &&
                    data.get(j + 1).compareTo(data.get(j)) < 0 )
                j ++;
            // data[j] 是 leftChild 和 rightChild 中的最小值

            if(data.get(k).compareTo(data.get(j)) >= 0 )
                break;

            data.swap(k, j);
            k = j;
        }
    }

时间复杂度

对于有 n 个节点的堆来说,其高度 d = log2n + 1。 根为第 0 层,则第 i 层结点个数为 2i, 考虑一个元素在堆中向下移动的距离。

  • 大约一半的结点深度为 d-1 ,不移动(叶)。
  • 四分之一的结点深度为 d-2 ,而它们至多能向下移动一层。
  • 树中每向上一层,结点的数目为前一层的一半,而子树高度加一

堆有logn层深,所以插入删除的平均时间和最差时间都是O(logN)

优先队列(priority_queue)

普通队列是一种先进先出的数据结构,先放进队列的元素取值时优先被取出来。而优先队列是一种具有最高优先级元素先出的数据结构,比如每次取值都取最大的元素。

优先队列支持下面的操作:

  • a. 找出优先级最高的元素(最大或最小元素);
  • b. 删除一个具有最高优先级的元素;
  • c. 添加一个元素到集合中。
代码实现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MaxHeap<E> maxHeap;

    public PriorityQueue(){
        maxHeap = new MaxHeap<>();
    }

    @Override
    public int getSize(){
        return maxHeap.size();
    }

    @Override
    public boolean isEmpty(){
        return maxHeap.isEmpty();
    }

    @Override
    public E getFront(){
        return maxHeap.findMax();
    }

    @Override
    public void enqueue(E e){
        maxHeap.add(e);
    }

    @Override
    public E dequeue(){
        return maxHeap.extractMax();
    }
}

堆排序

理解了优先队列,堆排序的逻辑十分简单。

第一步:让数组形成堆有序状态;

第二步:把堆顶的元素放到数组最末尾,末尾的放到堆顶,在剩下的元素中下沉到正确位置,重复操作即可。

堆排序动画

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java研发军团 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【数据结构】简单认识:堆
堆是特殊的队列,不同于普通队列,从堆中取出元素是依照元素的优先级大小,而不是元素进入队列的先后顺序,也可以称堆为“优先队列”。
.29.
2022/11/15
1.3K0
【数据结构】简单认识:堆
Data Structure_二叉树_集合_堆_并查集_哈希表
叶子节点就是左右孩子都是空的,但是并不是每一颗树都像上图所示的那样这么规整,有些树树可以只有左孩子没有右孩子的。二叉树的节点一定会大于左节点的值小于右节点的值,每一个节点都要满足,所有每一个节点下面拿出来的树都可以作为一个二叉树。既然有大于等于了,那么这科树的元素一定要有可比较性才可以。
西红柿炒鸡蛋
2019/01/23
5920
java优先级队列(基于堆)
优先级队列(堆):按照优先级的大小动态出队(动态指的是元素个数动态变化,而非固定)
VIBE
2022/11/18
7800
java优先级队列(基于堆)
堆和优先队列
  我们在常见的线性结构中,已经知道什么是普通队列了,普通队列就是一种“先进先出,后进后出”的数据结构,即普通队列的出队顺序和入队顺序是一样的,但我们的优先队列,它的出队顺序和入队顺序无关,它的出队顺序是和优先级相关的,当然这个优先级我们可以自己定义。
程序员波特
2024/01/19
1830
堆和优先队列
自已做动画及编写程序搞清楚最大堆的实现原理
背景 二叉树是数据结构中的重点,也是难点。二叉树比数组、栈、队列等线性结构相比复杂度更高,想要做到心中有“树”,需要自己动手画图、观察、思考,才能领会其真谛。 在上篇文章《自己动手作图深入理解二叉树、满二叉树及完全二叉树》中,我们对完全二叉树有了一定认识,该文将对一种特殊的完全二叉树”最大堆”进行底层研究。 概念 堆(heap)通常是一个可以被看做一棵二叉树的数组对象。堆总是满足下列性质: 堆总是一棵完全二叉树。 堆中某个节点的值总是不大于或不小于其父节点的值; 最大堆 根节点最大的堆叫做最大堆
智慧zhuhuix
2020/08/14
4310
自已做动画及编写程序搞清楚最大堆的实现原理
数据结构 02
在《数据结构 01》一文中,说到了数组、链表、栈以及队列这几种基本的线性结构,接下来就一起来看看剩下的内容。
贪挽懒月
2019/01/28
4750
数据结构 02
数据结构之堆
1、优先队列,本身就是一种队列。普通队列,先进先出,后进后出。优先队列,出队顺序和入队顺序无关,和优先级有关,优先级高者先得,优先级高的先出队。
别先生
2020/03/20
6490
数据结构之优先队列
1、优先队列的底层实现可以使用最大堆进行实现,由于优先队列本身就是一个队列,所以可以复用队列的接口。
别先生
2020/03/19
3650
堆的由来:要从优先队列说起,优先队列的定义:一般的队列取出的值是先进先出,是按入队顺序去出的。那么优先队列则是按照元素的优先权的大小,比如总是取出一组数据中的最大数。那么优先队列如何实现呢??可以通过数组和链表实现,但是时间复杂度很高。如下:
废江_小江
2022/09/05
3040
堆
最大堆(MaxHeap)
首先我们堆中的数据使用数组排列的,所以添加一个元素就是在层序遍历的最右端,也就是最下面一层的最后添加一个元素。但是以数组来看就是在索引为10的地方添加一个元素。
玖柒的小窝
2021/12/15
4410
最大堆(MaxHeap)
动画 | 什么是二叉堆?
堆,又称为优先队列。虽然名为优先队列,但堆并不是队列。堆和队列是两种不同的数据结构,堆是树态的,队列是线性的。在队列中,我们可以向队列添加元素,取出的时候是按照进入队列的先后顺序取出元素的,先进先出;而在堆中,却不是按照元素添加的先后顺序,而是按照元素的优先级取出元素。
我脱下短袖
2019/12/23
7190
线段树(区间树)
最经典的线段树问题:区间染色   有一面墙 ,长度为n,每次选择一段儿墙进行染色,m次操作后,我们可以看见多少种颜色?
程序员波特
2024/01/19
2220
线段树(区间树)
關於用 Go 实现堆和堆操作,可能是最通俗易懂的讲解了
堆是一种树形数据结构,分为大顶堆和小顶堆,顾名思义,大顶堆就是堆顶(第一个元素)始终存放的是这组元素中的最大元素,小顶堆就是堆顶元素是最小元素。如果需要从一组对象中查找最大值或最小值,使用堆能够高效率的完成需求。
KevinYan
2022/05/23
4390
關於用 Go 实现堆和堆操作,可能是最通俗易懂的讲解了
文心一言 VS 讯飞星火 VS chatgpt (142)-- 算法导论12.1 2题
在二叉搜索树(Binary Search Tree, BST)和最小堆(Min Heap)中,元素的排列顺序都是根据其关键字的大小。然而,它们之间存在着重要的区别。
福大大架构师每日一题
2023/11/25
1860
文心一言 VS 讯飞星火 VS chatgpt (142)-- 算法导论12.1 2题
【数据结构与算法】二叉树概念与实现详解 && 堆的实现 && TOPK问题
​ 树是一种 非线性 的数据结构,它是由 n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
利刃大大
2025/02/03
1520
【数据结构与算法】二叉树概念与实现详解 && 堆的实现 && TOPK问题
数据结构与算法(4)——优先队列和堆什么是优先队列?堆和二叉堆LeetCode相关题目整理
听这个名字就能知道,优先队列也是一种队列,只不过不同的是,优先队列的出队顺序是按照优先级来的;在有些情况下,可能需要找到元素集合中的最小或者最大元素,可以利用优先队列ADT来完成操作,优先队列ADT是一种数据结构,它支持插入和删除最小值操作(返回并删除最小元素)或删除最大值操作(返回并删除最大元素);
我没有三颗心脏
2018/07/24
1.3K0
数据结构与算法(4)——优先队列和堆什么是优先队列?堆和二叉堆LeetCode相关题目整理
面试官:你会手撕小顶堆算法排序吗?
本文一起来研究个常见算法,但是你不一定会 。 什么是小顶堆 小顶堆是一种经过排序的完全二叉树, 其满足如下性质: 小顶堆中的任意父节点都比其两个孩子结点小 由上方性质又可以推导出如下性质: 小顶堆的
程序员小猿
2021/01/19
2.3K0
深入解析:树结构及其应用
树结构是计算机科学中一种重要且广泛应用的数据结构,它具有层级关系,被广泛用于解决各种问题。在本文中,我们将深入学习树的基本概念、遍历方式以及堆和优先队列的应用。
IT_陈寒
2023/12/13
3170
深入解析:树结构及其应用
Java常见排序算法详解——堆排序
要了解堆首先得了解一下二叉树,在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。 二叉树的每个结点至多只有二棵子树(不存在度大于 2 的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第 i 层至多有 2i - 1 个结点;深度为 k 的二叉树至多有 2k - 1 个结点;对任何一棵二叉树 T,如果其终端结点数为 n0,度为 2 的结点数为 n2,则n0 = n2 + 1。
Demo_Yang
2019/04/18
9660
Java常见排序算法详解——堆排序
堆排序与优先队列
(二叉)堆物理上是一个数组,逻辑上是一棵完全二叉树,树上的每一个结点对应数组中的一个元素。 设表示堆的数组 ,其包含两个属性:
hotarugali
2022/03/01
3520
相关推荐
【数据结构】简单认识:堆
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档