什么是堆
堆是一种特殊的树,它满足以下两点:
-
堆是一个完全二叉树
完全二叉树要求,除最后一层,其他层的节点个数都是满的,最后一次的节点都靠左排列。
-
堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值
当前节点的值是子树中的最大或最小值。
我们将每个节点的值都大于等于子树中每个节点值的堆,叫做“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫做“小顶堆”
我们来看一下例子:
在上面的四个实例中,我们根据以上两条规则,可以判断出:
第一个、第二个是大顶堆,第三个是小顶堆,第四个由于不是完全二叉树所以不是堆。
如何实现一个堆
由于堆是一个完全二叉树,而完全二叉树又适合用数组来存储。
数组下标为i
的节点,左子节点是下标为i*2
的节点,右子节点是下标i * 2 + 1,
它的父节点是下标为i/2
的节点。
对于堆,我们要实现的功能大约有这三个:插入,删除堆顶元素,获取堆内的元素,在这里的实现以大顶堆为例。
|
|
获取数据
这个比较简单,我们只要将数组的内容返回即可,但是我们在下标0
中没有存储元素,并且可能未填满存在空数据,所以我们可以从1
开始复制,复制元素数量的长度。
|
|
添加元素
由于我们使用数组,所以存在最大长度,当元素数量未超过最大长度时进行添加,当超过长度后我们需要进行一个策略判断,在这里先直接抛弃。
后面添加元素的时候需要满足堆的规则,就需要进行调整,让其满足堆的特性,这个过程称为堆化。
堆化实际上有两种,从下往上和从上往下。这里使用从下往上的堆化方法
|
|
删除堆顶元素
根据堆定义规则的第二条可以知道:堆顶元素存储的是堆中数据的最大值或最小值。
我们以上面的大顶堆为例,堆顶元素就是最大值。当需要删除堆顶元素时,就需要第二大的元素放到堆顶,第二个元素就是堆顶元素的子节点。当将第二大元素放到堆顶后,还需要依次填充从子节点添加到父节点的位置,这时虽然完成了删除,但容易出现数组空洞的现象。
以下面这个大顶堆为例:
所以这里删除的时候需要转换一下思路:
先将最后一个节点元素放到堆顶,然后再比较父节点于子节点的关系,对于不满足条件的进行替换,直到满足堆的定义为止。这其实是从上往下的堆化方法。
刚才的例子用这个方法再来实现一遍:
将这个思路转换为代码:
|
|
至此就完成了大顶堆的数据访问,添加元素,删除堆顶元素操作。
这里实现的是大顶堆,如果实现小顶堆,只要比较条件中进行一些更改即可完成。
在这里的添加元素,当超过数量时直接进行了抛弃,对于一些应用不符合,这一部分放在应用练习中进行操作。
堆排序
从上面的几个例子可以看出,拿到存放堆的数组遍历后的数据并不是有序的。所以不能直接返回堆数组。
由于堆顶元素是最大值,所以我们可以将最大值与最后一个元素n
进行交换,然后对前面的n-1
个元素重新建堆。堆化完成后再取堆顶元素,放到n-1
的位置,依次进行交换,堆化的过程,直至结束。
用下面的图来看一下流程:
用代码来实现一下:
|
|
这个实现中需要注意一点,即由于堆的特性,下标0
不存储元素,所以我们这里只会除首位外的元素排序。也可以再封装一层再操作。