预备知识:
完全二叉树的定义:一个深度为k数的二叉树(设根节点的深度为1),若二叉树深度从1到k-1层都是满的,而第k层的节点都集中在左边(即第k层不存在两节点之间有空缺),那么此数就被叫做完全二叉树。
完全二叉树有几个重要的性质(编号从根节点由1开始广度优先从左到右排):
编号为i的节点左儿子(如果有的话)的编号为2*i,右儿子(如果有的)的编号为2*i+1,父节点(如果有的话)的编号为i/2。由此还可知道i节点有父亲的充要条件是i/2>=1,有儿子的充要条件是i*2<=size(size为该完全二叉树最大点编号)
堆的概述:
堆(heap)用数组存储,可看做一个完全二叉树,数组元素的下标即树节点的编号,故可由上性质知a[i]的父亲(如果有的话)为a[i/2],左儿子和右儿子(如果有的话)分别为a[i*2]和a[i*2+1]。
大根堆:父亲>=儿子;小根堆:父亲<=儿子。由大小根堆定义知堆顶一定是大根堆里最大、小根堆里最小的元素。
堆的核心操作为put操作(向堆里添一个元素)和get操作(取出堆顶并删除)。
以小根堆为例讲解二操作:
put:1.在堆尾插入一个元素,设其为当前节点;
2.当前节点与其父亲(如果有的话,否则直接结束)比较;
3.若比父亲小,与父亲交换值,更新当前节点为父节点,并继续重复第2步:否则结束。
核心代码:
1 void put(int x) 2 { 3 heap[++heap_size]=x; 4 int now,next;//当前节点,父亲节点 5 now=heap_size; 6 while(now>1) 7 { 8 next=now>>1; 9 if(heap[now]>=heap[next]) return; 10 swap(heap[now],heap[next]); 11 now=next; 12 } 13 }
get:1.保存堆顶元素,让最后一个元素覆盖堆顶元素,堆的大小减一,设当前节点为根节点;
2.当前节点与其儿子(如果有的话,否则之间结束、返回保存的堆顶元素)中值最小的比较;
3.若比儿子大,交换它们的值,更新当前节点为该儿子节点,继续第2步比较;否则结束,并返回保存的堆顶元素。
核心代码:
int get() { int ans=heap[1]; heap[1]=heap[size--]; int now=1,next; while(now*2<=size) { next=now*2; if(next<size&&heap[next+1]<heap[next]) next++;//找到最小的儿子 if(heap[now]<=heap[next]) break; swap(heap[next],heap[now]); now=next; } return ans; }
如为大根堆,只需比较时让大的“下潜”就行。
时间复杂度:都是O(log n)
应用:
1.堆排序:
由大小根堆定义知堆顶一定是大根堆里最大、小根堆里最小的元素,便可将待排元素一个个put到一个队里,并一个个get出来,时间复杂度为O(n log n)。
2.STL优先队列(实质是堆):特点是可以自动排序,详情见大佬博客:
https://blog.csdn.net/qq_19656301/article/details/82490601
~~~
最后BB:
可见堆的时间复杂度都是最坏情况的复杂度,个人认为数据极好的话堆排序应该比sort快。。。 不过普通数据用sort准没错。。。
(日后可能更新)