前言:堆是实现优先级队列效率很高的数据结构,堆其实是一颗特殊的完全二叉树,用下标从1开始的数组表示最有效率。在JVM中,堆是用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配。堆是所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致new对象的开销比较大。

栈:内存空间小一些,栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。主要存放函数体的地址、函数的参数、局部变量临时变量等。

堆:内存空间大一些,主要存放一些通过new出来的对象或者malloc申请的内存空间。

一.优先级队列

1、优先级队列(priority queue)是0个或多个元素的集合,每个元素都有一个优先级或权。与FIFO结构的队列不同,在优先级队列中,元素出队列的顺序是由元素的优先级决定的。可以按优先级的递增顺序,也可以按优先级的递减顺序,但不是队列进入队列的顺序。

优先级队列允许的三种操作:

1)Insert

2)findMax或findMin,查找到优先级最大或最小的元素后返回

3)DeleteMax或DeleteMin,删除一个优先级最大或最小元素

在最小优先级队列中,查找和删除的都是优先级最小的元素;在最大优先级队列中,查找和删除的都是优先级最大的元素。优先级队列的元素可以有相同的优先级,对这样的元素,查找和删除可以按任意顺序处理。

2、几种实现

1)使用链表
可以使用一个简单链表,在表头以O(1)执行插入操作,并遍历该链表以删除最大/小元素,需要O(N)。
始终让表保持有序状态,这使得插入代价O(N),而DeleteMin为O(1)

2)使用二叉查找树
三种操作的平均时间都是O(lgN)
但是二叉查找树支持许多并不需要的操作。而且较二叉堆的数组表示,每个节点会多出两个额外的指针域内存空间用来指向孩子节点,所以空间复杂度略大。还有在最坏情况下的操作时间复杂度可能在O(n)。

3)使用二叉堆
不需要使用指针,以最坏情形O(lgN)支持上述三种操作。

总结:较之于队列,优先级队列的不同在于它每一次取值取的是队列中的最大(小)值,可以用链表和二叉树来实现,然而用链表会使他的时间复杂度变大,所以优先用二叉树来表示优先队列,这种优先级队列就叫“堆”。

二、堆的定义和存储

1、堆的定义

堆是一种特殊的树,一个堆需要满足如下两个条件:

  • 一个堆是一个完全二叉树;

  • 堆中每个节点的值都必须大于等于或者小于等于其子树中的每个节点。

第一条,完全二叉树要求,除了最后一层,其它层的节点个数都是满的,并且最后一层的节点都靠左排列。

第二条,也等价于,每个节点的值大于等于或者小于等于其左右子节点的值。

按照数据的排列方式可以分为两种:最大堆(大根堆)和最小堆(小根堆)。节点值大于等于其子树中每个节点值的堆称为 “大根堆”,节点值小于等于其子树中每个节点值的堆称为 “小根堆”。

图片转自前辈博客

上图中,第 1 个和第 2 个是大顶堆,第 3 个是小顶堆,第 4 个不是堆。而且,可以看到,对于同一组数据,我们可以构建多种不同形态的堆。

2、堆的存储

之前我们知道,完全二叉树比较适合用数组来存储,这样非常节省空间,因为不需要额外的空间来存储左右子节点的指针,单纯通过下标我们就可以找到一个节点的左右子节点。故二叉堆一般用数组来表示,这种基于1的数组存储方式便于寻找父节点和子节点。

图片转自前辈博客

可以看到,下标为 的节点的左子节点下标为 ,右子节点下标为 ,而父节点下标就为 取下整。

三、堆的插入、查找和删除操作

这里以最大堆的Java实现作为例子。

1、往堆中插入一个元素

往堆中插入一个元素后,我们需要继续保持堆满足它的两个特性。

如果我们将新插入的元素放到堆的最后,此时,这依旧还是一棵完全二叉树,但就是节点的大小关系不满足堆的要求。因此,我们需要对节点进行调整,使之满足堆的第二个特性,这个过程称为堆化(heapify)。

堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比然后交换。

我们从新插入的节点开始,依次与其父结点进行比较,如果不满足子节点值小于等于父节点值,我们就互换两个节点,直到满足条件为止。这个过程是自下向上的,称为从下往上的堆化方法。

Java代码:

/** 最大堆*/public class maxHeap19
{private int[] heap; // 数组,从下标 1开始存储数据private int n;  // 数组heap的容量private int heapSize; //堆的元素个数public void maxHeap19(int capicity){heap = new int[capicity + 1];n = capicity;heapSize = 0;}private void swap(int[] nums,int i,int j){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}/** 1、大根堆的插入*/public void insert(int data) {//堆满了,这里其实可以增加数组长度if(heapSize>= n) return; //为元素data寻找插入位置int i = ++heapSize;heap[i] = data;while (i!=1 && heap[i] > heap[i/2]) { // 自下往上堆化swap(heap, i, i/2); // swap()函数作用:交换数组中下标为 i和 i/2 的两个元素i = i/2;}}}

2、在堆中查找一个最大/最小元素

  /** 2、查找最大元素*/public int getMax(){return heap[1];}

3、删除堆中最大/最小元素

假设我们构建的是大根堆,那么堆顶元素就是最大值。当我们删除堆顶元素后,就需要把第二大元素放到堆顶,而第二大元素肯定是其左右子节点中的一个。然后,我们再迭代地删除第二大节点,以此类推,直到叶子节点被删除。

但是,这个方法有点问题,删除堆顶元素后堆就不满足完全二叉树的条件了。

实际上,我们稍微改变一下思路,就可以解决这个问题。删除堆顶元素后,我们将最后一个结点放到堆顶,然后再依次进行对比,将这个结点交换到正确的位置即可。这个过程是自上而下的,称为从上往下的堆化方法。

Java代码:

/** 3、大根堆的删除*/public int removeMax() {if(heapSize == 0) return -1; //堆中没有元素heap[1] = heap[++heapSize];//删除堆顶元素后,我们将最后一个元素放到堆顶heapSize--;//重新建堆heapify(heap,heapSize,1);return 1;}private void heapify(int[] a, int n, int i) { // 自上往下堆化while(true) {int maxPos = i;if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;//如果右孩子比左孩子大if (i*2+1 <= n && a[maxPos] < a[i*2+1])maxPos = i*2+1;//根节点比左右孩子节点都大if (maxPos == i) break;swap(a, i, maxPos);i = maxPos;}}

总结:一棵包含 个节点的完全二叉树,树的高度不会超过 。而堆化的过程是顺着结点所在的路径进行比较交换的,所以堆化的时间复杂度和树的高度成正比,也就是 ,也即往堆中插入和删除元素的时间复杂度都为

四、左高树

1、出现原因

堆结构是一种隐式数据结构(implicit data structure),用完全二叉树表示的堆在数组中是隐式存贮的(即没有明确的指针或其他数据能够重构这种结构)。由于没有存贮结构信息,这种描述方法空间利用率很高,事实上没有空间浪费。尽管堆结构的时间和空间效率都很高,但它不适合于所有优先队列的应用,尤其是当需要合并两个优先队列或多个长度不同的队列时。因此需要借助于其他数据结构来实现这类应用,左高树(leftist tree)就能满足这种要求。

2、扩充二叉树

扩充二叉树是二叉树中的一种,是指在二叉树中出现空子树的位置增加空树叶,所形成的二叉树。它有一类特殊的节点叫外部节点,用来代替树中的空子树,其余节点叫做内部节点。

增加外部节点的二叉树被称为扩充二叉树。

3、左高树定义和应用

①定义

左高树是一棵扩充二叉树,且如果该二叉树不空,则对其中的每个内部结点x,都有左儿子到一个外部结点的最短路程长度大于或等于右儿子到一个外部结点的最短路程长度。

令s(x)是从节点x到其子树的外部节点的所有路径中最短的一条。根据s(x)的定义,若s(x)是外部节点,则s的值为0;若x为内部节点,则s的值为:min{s(L),s(R)}+1,其中L和R分别为x的左右孩子。

②应用

左高树的一个应用是合并操作,应用场景是:当某个优先队列的服务器关闭时,就需要将其与另一个正在运行服务器的优先队列合并。如果两个队列的元素总数为n,则一般的堆结构的复杂度为O(n),但是左高树可以达到O(logn)。插入和删除操作都可以通过合并操作来完成。

4、左高树的种类

1)高度优先左高树

当且仅当一棵二叉树的任何一个内部节点,其左孩子的s值大于等于右孩子的s值时,该二叉树为高度优先左高树(height-biased leftist tree, HBLT)。

2)重量优先左高树

当且仅当一棵二叉树的任何一个内部节点,其左孩子的w值大于等于右孩子的w值时,该二叉树为重量优先左高树(weight-biased leftist tree, WBLT);最大(小)WBLT 即同时又是最大(小)树的WBLT。

3)最小左高树

最小(最大)左高树是一棵左高树,其中的每个内部结点的关键字值不大于(不小于)该结点的儿子结点的关键字值。

对于最小左高树的操作,插入和删除最小元素操作都可以通过合并操作来完成。要把元素x插入到左高树A中,先建立一棵只有一个元素x的最小左高树B,再合并最小左高树A和B。要从一棵非空最小左高树A删除最小元素,则只需合并最小左高树A的左子树和右子树,再把最小左高树的根结点删除。

以下重点介绍最小左高树的合并操作。

假设要合并最小左高树A和B,首先,沿着A和B的最右路径,得到一棵包含A和B所有元素的二叉树(注意:这里只是得到二叉树,而不是最小左高树)。使得该二叉树具有以下性质:所有结点的关键字都不大于其儿子结点关键字。必要时交换结点的左、右子树,将其转化为最小左高树。

合并操作

1、假设合并最小左高树A和B,首先比较两棵树根结点的关键字值,以最小的关键字作为新二叉树的根结点。

2、保留A的左子树不变,将其右子树与最小左高树B合并,合并后的二叉树成为新的A的右子树。

3、把二叉树转换为最小左高树从最后一个修改结点(注意:这里不是最后结点)开始,回溯到最终的树根结点,使得路径上的所有结点满足不等式:shortest(left_child())>= shortest(right_child()

五、堆的应用

常见应用场景有堆排序、TopN问题和霍夫曼编码。

Java实现哈夫曼编码算法

数据结构基础21:堆相关推荐

  1. 数据结构基础(19) --堆与堆排序

    完全二叉树 首先让我们回顾一下完全二叉树的两个性质: 性质1:具有n个结点的完全二叉树的深度为[logn](向下取整)+1. 性质2:若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n  ...

  2. 数据结构基础(21) --DFS与BFS

    DFS 从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到(使用堆栈). //使用邻接矩阵存储的无向图的深度 ...

  3. 算法与数据结构基础 - 堆(Heap)和优先级队列(Priority Queue)

    堆基础 堆(Heap)是具有这样性质的数据结构:1/完全二叉树 2/所有节点的值大于等于(或小于等于)子节点的值: 图片来源:这里 堆可以用数组存储,插入.删除会触发节点shift_down.shif ...

  4. 一周刷爆LeetCode,算法da神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解 笔记

    一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解 笔记 教程与代码地址 P1 出圈了!讲课之外我们来聊聊 ...

  5. 求单链表的最大值与原地逆转_数据结构基础复习09.ppt

    数据结构基础复习09.ppt 数据结构考研辅导 基础复习 浙江大学计算机学院 内容提纲 考研概述 考察目标理解数据结构的基本概念 掌握数据结构的逻辑结构 存储结构及其差异 以及各种基本操作的实现 在掌 ...

  6. 【数据结构基础】之数组介绍,生动形象,通俗易懂,算法入门必看

    前言 本文为数据结构基础数组相关知识,下边将对数组的定义.性质及结构,数组的各种玩法如循环遍历数组.查找数组最大值.数组元素的位移等,二维数组的定义及用法等进行详尽介绍~ Java全栈学习路线可参考: ...

  7. 考研数据结构の基础概念

    考研数据结构の基础概念 第一章 绪论 第二章 线性表 第三章 栈与队列 第四章 串 第五章 矩阵与广义表 第六章 树 第七章 图 第八章 排序 第九章 查找 第一章 绪论 1.数据:是对客观事物的符号 ...

  8. 翻译:程序员数据结构基础:选择正确的数据结构

    本文转载自GameDev.net,仅供学习交流.因为刚刚开始学习翻译,难免有些疏漏,如果有哪些地方翻译的不正确,请不吝告知,万分感谢. 原文链接:http://www.gamedev.net/page ...

  9. “数据结构基础”系列网络课程主页

    #前言 自从下决心要解决学生动手能力差的问题,开始了课程实践资源的建设之旅:自迷上了翻转课堂,所教课程的视频,也就逐渐形成了体系.在为我自己的校内学生服务的同时,也希望能够让更多人有机会用到. 自全身 ...

  10. 【数据结构与算法 - 数据结构基础】什么是数据结构?

    [数据结构与算法 - 数据结构基础]什么是数据结构? 文章目录 [数据结构与算法 - 数据结构基础]什么是数据结构? 1 数据结构包含的三个方面 1.1 数据的逻辑结构 1.1.1 线性结构 数组[A ...

最新文章

  1. Android 隐式跳转(Activity的隐式跳转)
  2. mysql format函数对数字类型转化的坑
  3. webstorm javascript IDE调试
  4. JS类型判断、对象克隆、数组克隆
  5. MATLAB-fminsearch函数的使用
  6. 解决zabbix-agent二进制班不能连接使用docker搭建的zabbix-server
  7. GitHub标星14k:超详细的人工智能专家路线图
  8. VTK:PolyData之GetMiscPointData
  9. linux初学者-普通磁盘分区篇
  10. orbeon form 的架构简介 - 如何访问用户通过 form 存储的数据
  11. 【渝粤题库】陕西师范大学201341 刑事诉讼法学作业
  12. 歌谣带你看java面试题
  13. 编辑器Ultraedit快捷键
  14. 【转载】.NET设计模式之抽象工厂模式(Abstract Factory)
  15. mysqld.exe
  16. Python字符串isidentifier()
  17. 【C/C++】【VS开发】结构体存储空间数据对齐说明
  18. 敏捷思维-架构设计中的方法学(11)精化和合并
  19. matlab iir滤波器参数,[Matlab]IIR滤波器参数
  20. 彩扩机项目--开关滤波进阶,电机驱动桥,死区,三极管搭建反向电路

热门文章

  1. 概率+思路--luoguU50923 听我说,海蜗牛
  2. VS的ncb、pdb文件分析
  3. ESD门禁闸机系统的功能说明
  4. 摆脱对PC、服务器的依赖,英特尔“悄悄”在人工智能布下两颗棋子
  5. python get hist data_[宜配屋]听图阁
  6. 小白的Python 学习笔记(一)List 常用方法汇总
  7. 关于数学模拟软件无法在win10环境下运行的解决办法
  8. 携程apollo系列-客户端集成
  9. 揭刘翔离婚内幕:葛天假怀孕骗婚后一直分居
  10. python怎么在列表的每一项元素前面加上数字序号?