#CSPS/数据结构 # 二叉堆 ## 完全二叉树的存储 可以使用数组模拟链表的方式(记录左右儿子编号)来实现完全二叉树的存储。 ```cpp struct Node { int l, r; // 左右儿子编号 } a[MAXN]; ``` 实际上可以按顺序给完全二叉树编号,此时编号为 $i$ 的结点的左儿子、右儿子、父亲编号为 $2i, 2i+1,\lfloor \frac{i}{2} \rfloor$。这种存储方式又被称为**堆式存储**。 ![](二叉堆-1.jpg) ## 堆 堆是一种支持动态快速查询最值的数据结构,支持以下几种操作: - 高效插入元素。 - 高效查询、删除最值。 - 高效修改指定元素、删除指定元素. 二叉堆(简称堆)是一棵完全二叉树,它具有如下性质: * 对于**小根堆**(**大根堆**)而言,父结点的值不大于(不小于)两个儿子的值。 堆的根又被称为堆顶,小根堆的堆顶元素是堆中最小的元素(如何证明?)。 可以使用数组直接存储堆。 ```cpp int a[MAXN], n; // 数组存储堆,堆的大小,堆中的编号从 1 开始 ``` ![](二叉堆-2.jpg) ## 基本操作 当对堆进行增删改查的操作时,可能会破坏堆的性质,也可能会让堆不再是完全二叉树,需要考虑如何维护。 下文先介绍两种基本操作:上浮和下沉。这两种操作用来保证堆的性质。本文讲解均以小根堆为例。 ### 上浮 当你修改堆中某元素让其变小时: * 如果元素有父亲,并且值比父亲更小,交换元素,然后继续执行该操作。 * 否则,停止上浮。 ![](二叉堆-3.jpg) ```cpp // 让堆中编号为 x 的结点上浮 void Up(int x) { for (int i = x, j = i / 2; j && a[i] < a[j]; i = j, j = i / 2) { // j 为父亲编号 swap(a[i], a[j]); } } ``` 时间复杂度 $O(\log n)$。 ### 下沉 当你修改堆中某元素让其变大时: * 如果当前元素不是叶子,并且大于较小的儿子时,交换,然后继续执行该操作。 * 否则,停止下沉。 ![](二叉堆-4.jpg) ```cpp // 获取较小儿子编号 int Son(int x) { int i = 2 * x, j = 2 * x + 1; return i + (j <= n && a[j] < a[i]); } // 让堆中编号为 x 的结点下沉 void Down(int x) { for (int i = x, j = Son(i); j <= n && a[i] > a[j]; i = j, j = Son(i)) { // j 为较小儿子编号 swap(a[i], a[j]); } } ``` 时间复杂度 $O(\log n)$。 ### 查找最小值 即堆顶。 ```cpp int Top() { return a[1]; } ``` 时间复杂度 $O(1)$。 ### 插入元素 我们可以在完全二叉树的最后一个叶结点右边(即,堆的尾部)增加一个结点,保证堆仍然是一棵完全二叉树。 之后需要上浮来维护堆的性质。 ![](二叉堆-6.jpg) ```cpp void Push(int x) { a[++n] = x; Up(n); } ``` 时间复杂度 $O(\log n)$。 ### 删除最小值 如果直接删除堆顶,会使堆分裂为两个堆,而删除堆尾元素不需要维护。 我们可以将堆顶和堆尾元素进行交换,然后删除堆尾元素,保证堆仍然是一棵完全二叉树。 如果交换后的堆顶不满足堆的性质,需要进行下沉。 ![](二叉堆-5.jpg) ```cpp void Pop() { swap(a[1], a[n--]); Down(1); } ``` 时间复杂度 $O(\log n)$。 ## 用类来实现堆 对于比较复杂的数据结构,可以将其所存储的元素、属性、操作全部封装起来,使代码变得更具有逻辑。 ```cpp struct Heap { int a[MAXN], n; void Up(int x) { for (int i = x, j; (j = i / 2) && a[i] < a[j]; i = j) { swap(a[i], a[j]); } } int Son(int j) { return j + (j < n && a[j + 1] < a[j]); } void Down(int x) { for (int i = x, j; (j = Son(i * 2)) <= n && a[i] > a[j]; i = j) { swap(a[i], a[j]); } } void Push(int x) { a[++n] = x, Up(n); } void Pop() { swap(a[1], a[n--]); Down(1); } int Top() { return a[1]; } } h; ```