堆数据结构
堆的逻辑结构就是一颗完全二叉树(从上至下,从左到右依次编号的二叉树),底层可以使用数组实现。
堆的特点:
对于一个节点i (i是二叉树的节点的编号,或者数组的下标)
该节点的左孩子是:i * 2 + 1;
该节点的右孩子是:i * 2 + 2;
该节点的父节点是:(i -1) / 2;
大顶堆:
对于一个堆,如果所有的子树的根节点都大于他的左右子树的值,则这个堆是大顶堆。
小顶堆:
对于一个堆,如果所有的子树的根节点都小于于他的左右子树的值,则这个堆是小顶堆。
大顶堆和小顶堆常用语有序队列,比如优先级队列,定时任务队列。在java中的实现是:PriorityQueue
给定一个数组,如何构建一个大顶堆或者小顶堆
大顶堆(小顶堆类似):
1)将数组中的第一个元素取出放在堆顶。取出下一个数据和他的父节点比较,如果大于父节点则和父节点交换。如果还有父节点,继续比较交换,直到无法交换位置。
2)一次对所有的元素执行上述操作,最后得到的数据就是大顶堆。小顶堆相反比较交换。
交换比较的核心代码:
void heapInset(int num[], int index) {
//num[index] 当前节点, num[(index - 1)/2]父节点
while (num[index] > num[(index - 1)/2]){
swap1(num, index, (index - 1)/2 );
index = (index - 1) / 2;
}
}
如:[2,5,3,7,6,4]
步骤
1)将2放在堆顶,取出5和他的父((i-1)/2=(1-1)/2=0)节点比较,大于父节点就交换,如果还有父节点继续比较交换。交换后的数组变成:
[5,2,3,7,6,4]
- 取出3和堆顶的5比较,小于堆顶不动。继续继续取出7和其父((3-1)/2=1)节点比较,7大于2则交换,数组变成[5,7,3,2,6,4];7还有父(0节点),继续比较交换,数组变成[7,5,3,2,6,4]
3)继续取出6和其父(2节点)比较交换,数组变成[7,6,3,2,5,4]。最后取出4和其父节点(2)比较交换,最终用数组表示的大顶堆是[7,6,4,2,5,3]。对应的二叉树结构图是:
堆排序
1)将一个数组转换为大(小)顶堆,然后将堆顶的数据保存到临时变量中,将数组尾部的元素和顶部的元素交换,同时size - 1;这样就吧最大(最小)值移出数组了。
2)将新的堆(不包含最大或者最小元素),最顶元素和左右子树比较交换,循环该过程,直到形成一个新的大顶堆(或者小顶堆)。在重复步骤1,直到最后所有数据都移出,则排序完成。
时间复杂度:
- 创建堆的过程,由于是完全二叉树,每个元素加入平均耗时是logN,N个元素就是O(N*logN)
- 排序过程中,移出堆顶元素后,需要对新的堆重新构造大(小)顶堆,应为构建其实是对堆顶一个元素的行为,也是logN,,最多重复N次,就是O(N * logN)
- 所以总的时间复杂度是O(2NlogN),消除常数项就是O(NlogN)
空间复杂度O()