咱们来介绍另一种数据结构-堆,注意这里的堆和咱们Java语言,C++语言等编程语言在内存中的“堆”是不同的,这里的堆是一种树,由它实现的优先级队列的插入和删除的时间复杂度都为O(logN),这样尽管删除的时间变慢了,可是插入的时间快了不少,当速度很是重要,并且有不少插入操做时,能够选择用堆来实现优先级队列。html
【百度百科】堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆一般是一个能够被看作一棵彻底二叉树的数组对象。堆老是知足下列性质:程序员
将根节点最大的堆叫作最大堆或大根堆,根节点最小的堆叫作最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。面试
注意下面两种状况,b最后一层从左到右中间有断隔,那么也是不彻底二叉树。全部a是堆,而b不是。编程
图一是最大堆,图二是最小堆:api
堆的经典实现方式:使用数组来存储一个二叉堆,左节一次放大2倍,右节点则是2倍加1数组
堆不是容器,而是组织容器元素的一种特别方式。 只能肯定堆的范围,即开始和结束迭代器指定的范围。这意味着基本上没法对对进行遍历和查找。微信
所以,堆这种组织彷佛很是接近无序,因此,堆的操做通常为:数据结构
堆有两个原始操做用于保证插入或删除节点之后堆是一个有效的最大堆或者最小堆:架构
shiftUp()
: 若是一个节点比它的父节点大(最大堆)或者小(最小堆),那么须要将它同父节点交换位置。这样是这个节点在数组的位置上升。shiftDown()
: 若是一个节点比它的子节点小(最大堆)或者大(最小堆),那么须要将它向下移动。这个操做也称做“堆化(heapify)”。shiftUp 或者 shiftDown 是一个递归的过程,因此它的时间复杂度是 O(log n)。并发
基于这两个原始操做还有一些其余的操做:
insert(value)
: 在堆的尾部添加一个新的元素,而后使用 shiftUp
来修复对。remove()
: 移除并返回最大值(最大堆)或者最小值(最小堆)。为了将这个节点删除后的空位填补上,须要将最后一个元素移到根节点的位置,而后使用 shiftDown
方法来修复堆。removeAtIndex(index)
: 和 remove()
同样,差异在于能够移除堆中任意节点,而不只仅是根节点。当它与子节点比较位置不时无序时使用 shiftDown()
,若是与父节点比较发现无序则使用 shiftUp()
。replace(index, value)
:将一个更小的值(最小堆)或者更大的值(最大堆)赋值给一个节点。因为这个操做破坏了堆属性,因此须要使用 shiftUp()
来修复堆属性。上面全部的操做的时间复杂度都是 O(log n),由于 shiftUp 和 shiftDown 都很费时。还有少数一些操做须要更多的时间:
search(value)
:堆不是为快速搜索而创建的,可是 replace()
和 removeAtIndex()
操做须要找到节点在数组中的index,因此你须要先找到这个index。时间复杂度:O(n)。buildHeap(array)
:经过反复调用 insert()
方法将一个(无序)数组转换成一个堆。若是你足够聪明,你能够在 O(n) 时间内完成。堆还有一个 peek()
方法,不用删除节点就返回最大值(最大堆)或者最小值(最小堆)。时间复杂度 O(1) 。
Java中的类PriorityQueue,可参考: PriorityQueue使用。
堆排序就是利用堆删除的方法,将第一个元素和最后一个元素交换,而后size-1,在将堆里剩下的元素进行对调整,调整成一个新堆,一直到堆的size == 0,全部元素都已经排完了。
因为它在直接选择排序的基础上利用了比较结果造成。效率提升很大。它完成排序的总比较次数为O(nlog2n)。
堆排序须要两个步骤,一个建堆,二是交换从新建堆。比较复杂,因此通常在小规模的序列中不合适,但对于较大的序列,将表现出优越的性能。
在大量数据中找出其中最大的前k个数:能够利用堆,先将前k个数用来建堆,剩下的依次次与堆中第一个数比较(由于大堆中第一个数最大,小堆第一个数最小,这里以小堆找前k个最大的数为例),若是遇到比小顶堆的堆顶的值大的,将它放入堆中 最终的堆会是数组中最大的K个数组成的结构,在用堆排序就能够将最大的数按顺序排列。
内存中的堆和栈第一个区别就是申请方式的不一样:栈是系统自动分配空间的,而堆则是程序员根据须要本身申请的空间。因为栈上的空间是自动分配自动回收的,因此栈上的数据的生存周期只是在函数的运行过程当中,运行后就释放掉,不能够再访问。而堆上的数据只要程序员不释放空间,就一直能够访问到,不过缺点是一旦忘记释放会形成内存泄露。
申请效率的比较:栈由系统自动分配,速度较快。但程序员是没法控制的。堆是由new分配的内存,通常速度比较慢,并且容易产生内存碎片,不过用起来最方便。
申请大小的限制:
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2M(也有的说是1M,总之是一个编译时就肯定的常数),若是申请的空间超过栈的剩余空间时,将提示overflow。所以,能从栈得到的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是因为系统是用链表来存储空闲内存地址的,天然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。因而可知,堆得到的空间比较灵活,也比较大。
个人微信公众号:架构真经(id:gentoo666),分享Java干货,高并发编程,热门技术教程,微服务及分布式技术,架构设计,区块链技术,人工智能,大数据,Java面试题,以及前沿热门资讯等。每日更新哦!
参考资料: