堆
堆是一棵树。
这棵树的性质:一个节点的键值大于等于或小于等于其儿子的键值。
因为这个性质,可以在O(1)O(1)O(1)的时间查询最小值或最大值。
堆有很多种类型:二叉堆、配对堆、左偏树、斐波那契堆。
根据功能,还可以再分类:普通堆、可并堆、可持久化堆……
数据结构的树林十分繁密。
1、二叉堆
这是最基础,也是最常见最普遍应用最广的一种堆。
顾名思义,这是一棵二叉树。父亲的键值大于等于或小于等于左右儿子的键值。
点iii的左右儿子分别是2i,2i+12i,2i+12i,2i+1。
以小根堆为例:
- 如何插入一个数?
一种比较简单的方式就是把数值放在最底层、最右边的结点xxx。
若x.val<fax.valx.val<fa_x.valx.val<fax.val,交换xxx与faxfa_xfax。重复此操作。
因为这是一棵完全二叉树,时间复杂度是O(lgn)O(\lg_n)O(lgn)。
- 如何删除堆顶元素?
一种办法是把堆顶的值设到∞\infty∞,然后不断向下调整,最后删除。
还有一种办法是交换堆顶和最底层、最右边的结点,然后删掉最底层且最右边的元素(原堆顶)。
然后对新堆顶进行向下调整。
两种时间复杂度都是O(lgn)O(\lg_n)O(lgn)。
- 能不能快速地建堆?
一种方法是从结点1开始不断加入结点(向上调整)。
for(int i=1;i<=n;i++) up(i);
时间复杂度是lg1+lg2+lg3+...+lgn=Θ(nlgn)\lg_1+\lg_2+\lg_3+...+\lg_n=\Theta(n\lg_n)lg1+lg2+lg3+...+lgn=Θ(nlgn)。
换一个思考方向:从结点nnn开始不断向下调整。
for(int i=n;i>=1;i--) down(i);
可以证明,时间复杂度是O(n)O(n)O(n)。
证明一:自己想的。
最底层节点都只用进行一次操作,有n2\frac{n}{2}2n个结点。
倒数第二层最坏情况是两次操作,有n4\frac{n}{4}4n个结点。
……
第一层最坏情况是lgn\lg_nlgn次操作,有nn=1\frac{n}{n}=1nn=1个结点。
于是记时间复杂度为SSS:
S=n2×1+n4×2+n8×3+...+nn×lgn①①×2→2S=n×1+n2×2+n4×3+...+2×lgn②②−①→S=n+n2+n4+...+2+lgn =n(1+12+14+18+...+112n)+lgn=O(n)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ S=\frac{n}{2}\times1+\frac{n}{4}\times2+\frac{n}{8}\times3+...+\frac{n}{n}\times \lg_n①\\
①\times2\to2S=n\times1+\frac{n}{2}\times2+\frac{n}{4}\times3+...+2\times\lg_n②\\
②-①\to S=n+\frac{n}{2}+\frac{n}{4}+...+2+\lg_n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ =n(1+\frac{1}{2}+\frac{1}{4}+\frac{1}{8}+...+\frac{1}{\frac{1}{2}n})+\lg_n\\
=O(n)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
S=2n×1+4n×2+8n×3+...+nn×lgn①①×2→2S=n×1+2n×2+4n×3+...+2×lgn②②−①→S=n+2n+4n+...+2+lgn =n(1+21+41+81+...+21n1)+lgn=O(n)
证明二:网上搬的。
具有nnn个结点的完全二叉树,树高h=lgnh=\lg_nh=lgn,可知n=2hn=2^hn=2h。
层数从0开始。
最底层(h−1h-1h−1层)有2h−12^{h-1}2h−1个结点,每个结点进行一次比较,效率是2h−1×12^{h-1}\times 12h−1×1。
倒数第二层有2h−22^{h-2}2h−2个结点,每个结点进行两次比较,效率是2h−2×22^{h-2}\times 22h−2×2。
由此,可以写出第xxx层的效率=2x×(h−x)=2^{x}\times (h-x)=2x×(h−x)。
那么时间复杂度SSS:
S=2h−1×1+2h−2×2+2h−3×3+...+20×h①①×2→ 2S=2h×1+2h−1×2+2h−2×3+...+21×h②①−②→ S=2h×1+2h−1+2h−2+...+21+20×h =2h+1−1+h =O(2n−1+lgn) =O(n)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ S=2^{h-1}\times1+2^{h-2}\times2+2^{h-3}\times3+...+2^0\times h①\\
①\times2\to\ \ \ 2S=2^h\times1+2^{h-1}\times2+2^{h-2}\times3+...+2^{1}\times h②\\
①-②\to\ \ \ S=2^h\times1+2^{h-1}+2^{h-2}+...+2^1+2^0\times h\ \ \ \ \ \ \ \ \\
=2^{h+1}-1+h\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\
=O(2n-1+\lg_n)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\
=O(n)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
S=2h−1×1+2h−2×2+2h−3×3+...+20×h①①×2→ 2S=2h×1+2h−1×2+2h−2×3+...+21×h②①−②→ S=2h×1+2h−1+2h−2+...+21+20×h =2h+1−1+h =O(2n−1+lgn) =O(n)
两者的方法类似,思考过程有差异。
左偏堆
留坑