面试题答案
一键面试最小根堆的数据结构组织
- 数组存储:在C++实现中,通常使用数组来存储最小根堆的元素。对于一个具有
n
个节点的完全二叉树(最小根堆是一种完全二叉树),节点i
的左子节点索引为2 * i + 1
,右子节点索引为2 * i + 2
,父节点索引为(i - 1) / 2
(i > 0
)。 - 堆序性质:最小根堆满足堆序性质,即对于除根节点外的每个节点
i
,其值大于或等于其父节点的值。这意味着根节点总是堆中值最小的元素。在定时器管理场景中,这个值通常是定时器的到期时间。 - 插入与删除操作:
- 插入:当插入一个新的定时器时,将其添加到数组的末尾,然后通过“上浮”操作(
sift up
)将其调整到合适的位置,以维护堆序性质。具体过程是将新节点与其父节点比较,如果小于父节点,则交换位置,重复此过程直到满足堆序。 - 删除:删除操作通常是删除根节点(即到期时间最早的定时器)。删除根节点后,将数组末尾的节点移到根节点位置,然后通过“下沉”操作(
sift down
)将其调整到合适位置。下沉操作是将当前节点与其子节点比较,选择较小的子节点与之交换,重复此过程直到满足堆序。
- 插入:当插入一个新的定时器时,将其添加到数组的末尾,然后通过“上浮”操作(
相较于链表在定时器管理方面的优势
- 查找效率高:
- 链表:在链表中查找到期时间最早的定时器,最坏情况下需要遍历整个链表,时间复杂度为 $O(n)$,其中
n
是链表中定时器的数量。 - 最小根堆:最小根堆的根节点始终是到期时间最早的定时器,获取该定时器的时间复杂度为 $O(1)$。即使在插入和删除操作后需要调整堆结构,其时间复杂度也仅为 $O(\log n)$,因为堆的高度为 $O(\log n)$。
- 链表:在链表中查找到期时间最早的定时器,最坏情况下需要遍历整个链表,时间复杂度为 $O(n)$,其中
- 动态调整性能好:
- 链表:当有新定时器加入或现有定时器到期删除时,链表需要遍历找到合适位置插入或删除节点,插入和删除操作在最坏情况下时间复杂度为 $O(n)$。而且,如果链表无序,每次查找最早到期定时器都需要遍历。
- 最小根堆:插入和删除操作的时间复杂度为 $O(\log n)$。由于堆的结构特性,能够高效地动态调整定时器的位置,保持堆序性质,从而快速定位最早到期的定时器。
- 空间利用更紧凑:
- 链表:链表每个节点除了存储定时器数据外,还需要额外的指针(前驱和后继指针)来维护链表结构,这增加了额外的空间开销。
- 最小根堆:使用数组存储,仅需存储定时器数据,通过数组索引关系确定节点之间的父子关系,无需额外指针,空间利用更紧凑。