乐趣区

关于java:为什么堆化-heapify-只用-On-就做到了

heapify()

后面两篇文章介绍了什么是堆以及堆的两个基本操作,但其实呢,堆还有一个赫赫有名的十分重要的操作,就是 heapify() 了,它是一个很神奇的操作,

能够用 O(n) 的工夫把一个乱序的数组变成一个 heap。

然而呢,heapify() 并不是一个 public API,看:

所以咱们没有方法间接应用。

惟一应用 heapify() 的形式呢,就是应用
PriorityQueue(Collection<? extends E> c)

这个 constructor 的时候,人家会主动调用 heapify() 这个操作。

<span style=”display:block;color:blue;”> 那具体是怎么做的呢?

哈哈源码曾经裸露了:

从最初一个非叶子节点开始,从后往前做 siftDown().

因为叶子节点没必要操作嘛,曾经到了最上面了,还能和谁 swap?

举个例子:

咱们想把这个数组进行 heapify() 操作,想把它变成一个最小堆,拿到它的最小值。

那就要从 3 开始,对 3,7,5 进行 siftDown().

Step 1.

难堪 ????,3 并不必替换,因为以它为顶点的这棵小树曾经满足了堆序性。

Step 2.

7 比它的两个孩子都要大,所以和较小的那个替换一下。

替换实现后;

Step 3.

最初一个要解决的就是 5 了,那这里 5 比它的两个孩子都要大,所以也和较小的那个替换一下。

换完之后后果如下,留神并没有满足堆序性,因为 4 还比 5 小呢。

所以接着和 4 换,后果如下:

这样整个 heapify() 的过程就实现了。

好了难点来了,为什么工夫复杂度是 O(n) 的呢?

怎么计算这个工夫复杂度呢?

其实咱们在这个过程里做的操作无非就是替换替换。

那到底替换了多少次呢?

没错,替换了多少次,工夫复杂度就是多少。

那咱们能够看进去,其实 同一层的节点 最多替换的次数都是雷同的。

那么这个总的替换次数 = 每层的节点数 * 每个节点最多替换的次数

这里设 k 为层数,那么这个例子里 k=3.

每层的节点数是从上到下以指数增长:

$$\ce{1, 2, 4, …, 2^{k-1}}$$

每个节点替换的次数,

从下往上就是:

$$ 0, 1, …, k-2, k-1 $$

那么总的替换次数 S(k) 就是两者相乘再相加:

$$S(k) = \left(2^{0} *(k-1) + 2^{1} *(k-2) + … + 2^{k-2} *1 \right)$$

这是一个等比等差数列,规范的求和形式就是 错位相减法

那么
$$2S(k) = \left(2^{1} *(k-1) + 2^{2} *(k-2) + … + 2^{k-1} *1 \right)$$

两者相减得:

$$S(k) = \left(-2^{0} *(k-1) + 2^{1} + 2^{2} + … + 2^{k-2} + 2^{k-1} \right)$$

化简一下:

(不好意思我切实受不了这个编辑器了。。。

所以 heapify() 工夫复杂度是 O(n).

以上就是堆的三大重要操作,最初一个 heapify() 尽管不能间接操作,然而堆排序中用到了这种思路,之前的「抉择排序」那篇文章里也提到了一些,感兴趣的同学能够后盾回复「抉择排序」取得文章~至于堆排序的具体实现和利用,以及为什么理论生产中并不爱用它,咱们之后再讲。

如果你喜爱这篇文章,记得给我点赞留言哦~你们的反对和认可,就是我创作的最大能源,咱们下篇文章见!

我是小齐,纽约程序媛,终生学习者,每天晚上 9 点,云自习室里不见不散!

更多干货文章见我的 Github: https://github.com/xiaoqi6666…

退出移动版