上图是大顶堆,若是每一个节点小于等于其每一个子节点的值,那它就是小顶堆。
有趣的是,堆能够经过数组来实现。例如,数组 data = [50 43 49 15 28 40 30 5 10 23 15 20] 能够表示上面的堆。数组中元素的排放顺序表示节点按照从顶到底、每一层从左到右的顺序放置。堆之因此能够放到数组中,是由于它是一颗彻底二叉树。能够根据任意节点的下标,经过公式计算出子节点的下标。假设节点P的下标为i,那么节点P的左子节点下标为2i + 1,右子节点下标为2i + 2,相关证实过程能够看这里数据结构与算法-二叉树性质。咱们这里就以数组来实现堆结构,探讨相关的建立、插入、删除、运用的逻辑。
堆的建立有两种方式。第一种是自上向下从一个空数组中建立堆,第二种是自下向上将已有的数组调整为堆。首先探讨自上向下的逻辑。
从一个空数组开始,每个新节点都放到当前堆的末尾,若是发现插入节点以后破坏了堆的特性,那么将新节点与其父节点进行交换,直至从新调整为堆。假如如今有一段数据流:
49 50 43 15 28 40 5 25 10 23 30 20 55
核心逻辑在于每次插入新节点以后,若是破坏了堆的结构,只要和父节点进行交换,直至调整为堆便可。该算法的时间复杂度有O(nlgn),不是很理想,数据量比较大时不适合这种方案。
一颗完整的二叉树能够分解成根节点,左子树,右子树。对于左右子树又能够分解成更多的子树。若是咱们将树中的每一个节点都看作一颗树,只要每棵树符合堆的特性,那么整棵树也是符合堆的特性的。基于以上思想,前人提出了一种自下向上的建立堆地方式。树中最小的子树实际上是叶子节点,因为叶子节点没有子树,能够认为是符合堆的特性的。因此,咱们就从最后一个非叶子节点开始,按照从下向上,从右到左的顺序,调整每一个子树符合堆的特性,直到根节点,那么整棵树就成了堆。
假设一共有n的元素,最后一个非叶子节点的下标是多少呢?答案是n/2 -1。证实以下:
假设堆中度为0、度为一、度为2的节点分别有n0、n一、n2个,那么n = n0 + n1 + n2,又知道n0 = n2 +1,证实方式在数据结构与算法-二叉树性质。那么n = 2*n2 + n1 +1。
那么最后一个非叶子节点的下标为n2 - 1,即(n - n1 - 1)/2 - 1= n/2 - 3/2,因为n = 2*n2 + 1是奇数,因此n/2 - 1向下取整等于n/2 - 3/2。
那么最后一个非叶子节点的下标为n2,即(n - n1 -1)/2 = n/2 - 1。
对于彻底二叉树来说,n1要么为0要么为1。所以,综上所述,最后一个非叶子节点的下标为n/2 - 1。
既然找到了最后一个非叶子节点P,那就从P节点开始自下向上,自右向左的调整每个遇到的子树为堆,最终整棵树成为堆。
假设如今存在数组data = [39 50 43 15 28 40 5 25 10 23 30 20 55],展开以后是这样的:
该算法的时间复杂度是O(n),一般状况下优于自上向下建立堆的方式。
在堆中插入一个新的元素,一般会放入到数组的末尾,若是插入到头部或者中间某个位置,有两个缺陷。第一,会移动数组里大量数据,第二,严重破坏堆结构,只能依靠自下向上的方式调整全部子树,才能保持整棵树的堆结构,得不偿失。
若是将新元素插入到数组末尾,仅仅有可能破坏少许子树的堆结构,也能在短期内调整完毕。假若有如下堆结构:

在末尾插入60节点,调整方式以下:
能够发现,不管是自上向下建立堆仍是自下向上调整堆,又或者在堆中插入新的元素,核心逻辑老是调整堆的过程。关键在于,二叉树中每一个节点老是有两个身份,既是某颗子树(P子树)的根节点,也是其父节点所在树(Q子树)的子节点。调整堆的过程其实就是既要保证P子树是堆,也要保证Q子树是堆。算法
在堆中删除某个元素,一般咱们会删除堆的根节点,由于它是有价值的,要么是最大值要么是最小值。为了不删除头结点以后数组元素大量移动,前人想出一个巧妙的方式。那就是将最后一个叶子节点和根结点进行交换。最后一个叶子节点成了根节点,根节点成了最后一个叶子节点。这时,咱们删除元素没必要移动数组数据,可是破坏了堆结构,怎么办呢?老样子,调整堆,调整方式和上面介绍的方法一致,就很少说了。
咱们固然能够删除数组中任一元素,方法和删除根节点是一致的。可是,咱们每每不会这么作,由于在堆中除了根节点,其余元素没有特别的地方,价值不大。
咱们已经了解堆的建立、添加、删除,那它有什么用处呢?
答案是优先队列。对于堆来讲,新元素老是插入到数组末尾,被删除元素老是根节点,而且要么是最大的要么是最小的,这不就是优先队列吗?在数据结构与算法-栈与队列中,咱们推荐使用链表来实现队列,这对于普通的队列是能够的,由于普通队列保持的是数据插入时的次序。可是对于优先队列就不合适了,数据插入到优先队列以后,须要依靠优先级排序,从队列弹出的数据老是优先级最大(最小)的元素。若是使用链表来实现优先队列,操做复杂度是O(n),可是若是使用数组,将数据按照堆结构排列,那么操做复杂度仅仅有O(lgn),这是很是吸引人的。而且,标准库中的优先队列就是以向量实现的。
算法逻辑很简单,就是不停的删除根节点、调整堆的过程。那么,数据就会以从大到小或者从小到大的顺序从堆中删除。堆排序的操做复杂度有O(nlgn),算不上很优秀,但至少比冒泡、插入这些算法要好用。
堆的内容到这里已经探讨完毕,更多内容就须要在实践中摸索了。