【数据结构】【堆】堆的介绍及插入、删除操作


感谢简书[唐先僧]的博文数据结构:堆(Heap),本博文有部分借鉴内容。

一、堆的介绍

堆是一个有固定顺序的完全二叉树,通常用数组来表示。

1. 用数组表示堆,如何区分父节点和子节点?

下图是一个堆常用的编号方式示意图:根节点编号为0,根节点的左子节点编号为1、右子节点编号为2,再往下是3、4、5、6……即按照从上往下、从左往右的顺序编号。

在数组中,就按照如上所述的下标进行保存,如上图保存为数组就是{10, 20, 15, 25, 50, 30, 40, 35, 45}。

在这样一个数组中,下标为i的节点的父节点的下标就是 (i - 1) / 2(除法向下取整);而下标为i的节点的左子节点的下标2 * i + 1右子节点的下标是2 * i + 2。

示例:

第3个元素(25)的左子节点是第**2 * 3 + 1 = 7**个元素(35),
而它的右子节点是第**2 * 3 + 2 = 8**个元素(45)。

2. 堆的种类?

堆一般分为两种,即最大堆和最小堆。

最大(小)堆中每个节点的父节点一定比当前节点大(小);

或者说,每个节点的左右子树中的所有节点,一定比这个节点小(大)。

如下图所示则是一个最大堆:

扫描二维码关注公众号,回复: 12537206 查看本文章

二、堆的插入、删除操作

为了保证我们在插入、删除任意节点后,最大(小)堆仍然能保证它的最大(小)特性,需要研究一套既定的、有规律可循的操作来完成插入和删除。

1. 几个基本操作

在讲堆的插入与删除之前,要明确堆的几个基本操作,而插入和删除全都是由这些基本操作组成的:

shiftUp():如果一个节点比它的父节点大(最大堆)或者小(最小堆),那么需要将它同父节点交换位置。这样是这个节点在数组的位置上升。

shiftDown():如果一个节点比它的子节点小(最大堆)或者大(最小堆),那么需要将它向下移动。这个操作也称作“堆化(heapify)”。

shiftUp 或者 shiftDown 是一个递归的过程,所以它的时间复杂度是 O(log n)。

2. 堆的插入

首先,在堆的第一个空白位置处插入这个新元素,然后递归调用上述的shiftUp()操作,直到它的父节点比它大(最大堆)或小(最小堆)。

示例:
我们通过一个插入例子来看看插入操作的细节。我们将数字16插入到这个堆中:
在这里插入图片描述
第一步是将新的元素插入到堆的第一个空白位置处。则堆变成:
在这里插入图片描述
不幸运的是,现在堆不满足堆的属性,因为 2 在 16 的上面,我们需要将大的数字在上面(这是一个最大堆),为了恢复堆属性,我们需要交换162

现在还没有完成,因为 10 也比 16 小。我们继续交换我们的插入元素和它的父节点,直到它的父节点比它大或者我们到达树的顶部。这就是所谓的 shiftUp(),每一次插入操作后都需要进行。它将一个太大或者太小的数字“浮起”到树的顶部。
最后我们得到的堆:
在这里插入图片描述
现在每一个父节点都比它的子节点大,满足了最大堆的属性。

3. 堆的删除

为了将这个节点删除后的空位填补上,首先要将本堆中最后一个元素的值**(假设为value)**复制到此位置(此时相当于删除了这个位置原来的节点),然后再把本堆最后一个元素删掉(因为它已被赋值到被删节点的位置),然后在被删位置处,用此位置当前的值value和它的父节点、子节点去比较,如果它与父节点的关系破坏了最大(小)堆,则递归调用shiftUp()来修复;如果它与子节点的关系破坏了最大(小)堆,则递归调用shiftDown()来修复。

示例:
我们将这个树中的 (10) 删除:
在这里插入图片描述
现在顶部有一个空的节点,怎么处理?
在这里插入图片描述
当插入节点的时候,我们将新的值返给数组的尾部。现在我们来做相反的事情:我们取出数组中的最后一个元素,将它放到树的顶部,然后再修复堆属性。

现在被删除位置处的元素值为1,由于它没有父节点,所以我们只把它与子节点进行比较,看是否违反了最大堆的属性:现在有两个数字( 7 和 2)可用于交换。我们选择这两者中的较大者称为最大值放在树的顶部,所以交换 7 和 1,现在树变成了:
在这里插入图片描述
继续堆化直到该节点没有任何子节点或者它比两个子节点都要大为止。对于我们的堆,我们只需要再有一次交换就恢复了堆属性:
在这里插入图片描述
如上,就完成了堆的删除操作。

猜你喜欢

转载自blog.csdn.net/qq_39642978/article/details/111551332