- 堆的特点
堆可以理解为是一种特殊二叉树,但其实大多数用数组实现的因为有如下特点
1、堆中某个节点的值总是不大于或不小于其父节点的值
2、堆总是一棵完全二叉树
第一个点也就说根节点要么是最大值要么是最小值,因为子节点都会全部小于或者大于根节点,第二点说明可以用数组来表示,因为完全二叉树第一层有一个元素,第二成有两个,4,8……,所以用数组表示时,前一个做顶后两个做第二层接着四个做第三层,由于是一颗完全的二叉树所以数组可以从前到后全部装满元素。下面都是以根节点最大为例子讲解
完全二叉树:即只能是最后一层不满其他层必须为满的,而且最后一层必须先从左到右放数据项直到满为止。 - 插入
堆的插入时直接放在最后一个元素即可,之后向添加位置的父节点进行比较如果大于父节点,则和父节点交换再进行父节点的比较直到根节点(向上筛选),或者小于为止即插入完成。 - 删除
堆的删除时从根节点删除的,由于堆的提出是为了实现一种高效的优先队列来的,所以删除就是移除最大的点即根节点,先移走根节点,再把最后一个元素放到根节点处,之后再和子节点进行比较如果小于则交换直到根节点或者大于子节点的位置(向上筛选),拿最后一个节点进行补充被移走的根节点是因为这样保证了任然是一颗完全二叉树方便插入时直接插入结尾就可以不用寻找哪里有坑再插入。 - 弱序
优先队列可以用数组然后排序实现,也可以通过堆实现,用数组排序实现的话可以做到,当元素相同时先进的会先出,用堆实现的话会出现相同元素时先进后出。但是优先队列讲的是优先的先出来,优先一样的谁先出来都没关系,因为优先级都一样不用队列了,讲的是一种优先的排在前面而不是一定要保证先进先出。优先队列就是优先级当作队列,优先级一样不用当队列。用数组排序直接实现的话删除快直接O(1),但是插入慢,因为插入必须找到合适的位置插入,而用堆从上面可以看出插入和删除都平均对树进行遍历一遍复杂度均为O(logn)。弱序表现在像排序数组一样的话整个数组从左到右是排序好的,而堆就没那么好的顺序,只可以保证父节点比子节点大,即沿着一个路劲排序,并不会保证上层大于下层等。弱序也是保证堆的效率。 - 基于数组实现堆
基于二叉树实现堆实现起来简单易懂,但是还要保存子节点的引用浪费空间,而因为堆这种数据结构元素位置固定结构紧凑很容易通过数组实现,插入和删除时按照堆的规则来就可以不用保存下一个节点的引用,而是固定下一节点在对应固定的数组下标。比如下标0表示堆的根节点,1和2表示子节点,3和4表示1的子节点,5和6表示2的子节点……。 - 堆排序
基于堆的排序空间复杂度为O(1)和时间复杂度最坏最好都是O(nlogn),效率高,堆保证移除即弹出的是堆里面最大的数据项,也就是说将待排序的数据建立一个堆,然后按移除即弹出的顺序排列即为排序好的数据。建立堆需要n次插入平均每个logn的时间复杂度,移除堆需要n次移除平均每个logn的时间复杂度。又因为堆实现优先队列的话会出现相同元素时先进后出,所以不稳定的。 - 基于数组的堆实现
基于数组的堆,由于数组是固定的,当移除一个数据项时就多出一个空位,用于放排序好的数据项,移除堆中一个元素就腾出一个位置放排序好的数据,所以不用额外的空间。
一个数组乱序实现堆时,如果通过一个一个元素的插入形成堆可以做到但是有一个更好的技巧,效率更高。两个子堆正确的可以通过向下筛选方式形成正确的堆。比如移除时即这种操作。从非叶子节点开始遍历向下筛选直到根节点即完成堆的建立。所以只需要遍历一半的元素即可,即只需要做向下筛选n/2 次。比如下图二,从6-5-4-3-2-1-0逐个做向下筛选即可(是删除时的向下筛选,不是插入时要做的向上筛选)。
下面基于乱序数组的排序示例