%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% # 优先队列 「**优先队列(priority queue)**」是一种**抽象数据结构**,定义为**具有==优先级排序==的队列**,每次出队的是**队列中优先级最高的元素**。 优先队列的具体实现可以是:**二叉堆**、**斐波那契堆**等。 「**二叉堆**」通常用于**实现优先队列**: - "**大顶堆**" 相当于按 **==从大到小== 顺序出队的优先队列("元素==大==的优先级高")** - "**小顶堆**" 相当于按 **==从小到大== 顺序出队的优先队列("元素==小==的优先级高")** <br> # 堆的定义(二叉堆) 从**逻辑**上来说,「**==堆(heap)==**」被定义为一种**满足特定条件**的 **==完全二叉树==**,分为**两种类型**: - 「**大顶堆(max heap)**」:**完全二叉树**上**每个节点的值都 `>=` 其子节点的值**。 - 「**小顶堆(min heap)**」: **完全二叉树**上**每个节点的值都 `<=` 其子节点的值**。 **大顶堆**中,**根节点为树上的==最大值==**; **小顶堆**中,**根节点为树上的==最小值==** ![[_attachment/00-数据结构与算法笔记/数据结构/堆(二叉堆).assets/IMG-堆(二叉堆)-54BC0BD3A4A8EB8209AB3D99EBA3260C.png|660]] <br> # 堆的存储实现 堆的 "**逻辑结构**" 是 "**==完全二叉树==**",其**底层数据结构**通常为 "**==数组==**", **堆**按 **==层序遍历==** 依次存放于数组中: - **首项**元素 `arr[0]` 为**堆顶/树根**; - 给定**任意节点下标 $i$**: - 其 **父节点下标**为 $\Large \left\lfloor\frac{i-1}{2}\right\rfloor$ ; - 其**左孩子节点下标**为 $2i+1$,**右孩子节点下标**为 $2i+2$。 - **最后一个节点 `arr[n-1]`** 的**父节点**即为 "**最后一个内部节点**"。 ![[_attachment/00-数据结构与算法笔记/数据结构/堆(二叉堆).assets/IMG-堆(二叉堆)-6A1E7AEAB289B16A53902D7C027D58C7.png|751]] # 堆的相关操作 两个**最基本的底层操作**(以**大顶堆**为例): - **上滤 `SiftUp`**:**给定==叶节点== $i$,若其大于父节点,则与父节点交换,持续地上移**; - **下滤 `SiftDown`**:**给定==内部节点== $i$,若其小于子节点,则与最小子节点交换,持续地下移**。 基于上述操作所实现的**堆的外部接口**: - **==`HeapPush` 入堆==**: 将元素追加到**列表尾**,**执行上滤 `SiftUp`**。 - **==`HeapPop` 出堆==**: 将**堆顶根节点**交换到**列表尾**并`pop`,**对新的根节点执行下滤 `SiftDown`**。 - **==`Heapify` 堆化==**:从**最后一个内部节点**开始直至**根节点**,依次执行`SiftDown `建堆 (**==自底向上==建堆**) - **==`Heapsort` 堆排序==**:将**堆顶元素**与**尾部元素**交换并**将尾部元素视作排除与堆外**,**对新的根节点执行下滤`SiftDown`** 其中,**只有 `HeapPush` 基于上滤 `Siftup` 操作**,**其余三项均只基于下滤 `SiftDown` 操作**。 `heapsort`与 `heappop` 的思路一样, 将旧根交换到尾部再对新根执行`sift_down`, 只不过 `heapsort` 是**通过减小`size`变量收缩堆的边界来模拟缩减尾部**, 而 `heappop`是真的移除尾部元素。 ### 堆的操作效率 | 操作 | 说明 | 时间复杂度 | | ----------- | ------------- | ----------- | | `push()` | 元素入堆 | $O(\log n)$ | | `pop()` | 弹出堆顶元素 | $O(\log n)$ | | `top()` | 获取堆顶元素 | $O(1)$ | | `heapify()` | 建堆/堆化(自底向上建堆) | $O(n)$ | <br> ## (1)建堆 两种方式: - **自顶向下建堆**:依次调用 `HeapPush` 将所有元素入堆,时间复杂度为 $O(n\log n)$; - **自底向上建堆**:调用 `Heapify` 完成建堆——**从底向上,对每个==内部结点==进行下滤操作,直至根节点**,时间复杂度为 $O(n)$ #### "自底向上" 建堆的实现 ```cpp // 自底向上堆化 // 堆化所有内部节点(堆对应的逻辑结构为完全二叉树, 数组中元素等同于按层序遍历得到, 所以最后一个父结点及之前全都是内部结点) void MaxHeap::Heapify() { if (arr.empty()) throw std::runtime_error("empty heap"); int last_parent = (arr.size() - 1) / 2; // 最后一个父节点, 也即最后一个内部节点. for (int i = last_parent; i >= 0; --i) { SiftDown(i); } } ``` #### "自底向上" 建堆的时间复杂度分析 `Heapify` 的时间复杂度为 $O(n)$。 ![[_attachment/00-数据结构与算法笔记/数据结构/堆(二叉堆).assets/IMG-堆(二叉堆)-538BC4C0B2F46EE7BE86795153D92F44.png|575]] ![[_attachment/00-数据结构与算法笔记/数据结构/堆(二叉堆).assets/IMG-堆(二叉堆)-F35231FDCB5172FFA3401DB610054F70.png|600]] ## (2)入堆 入堆 `HeapPush`: 将元素追加到**列表尾**,**执行上滤 `SiftUp`**。 ![[_attachment/00-数据结构与算法笔记/数据结构/堆(二叉堆).assets/IMG-堆(二叉堆)-049525AF9D608BB2ADAA1CE7719F83BD.png]] ## (3)出堆 出堆 `HeapPop`: 将**堆顶根节点**交换到**列表尾**并`pop`,**对新的根节点执行下滤 `SiftDown`**。 ![[_attachment/00-数据结构与算法笔记/数据结构/堆(二叉堆).assets/IMG-堆(二叉堆)-602A768C2FD89917D4BF090B1CF95546.png]] <br> # 堆的完整实现 ```cpp // 大顶堆. class MaxHeap { public: explicit MaxHeap(const vector<int>& arr) { this->arr_ = arr; heapify(); } void push(int val) { arr_.push_back(val); siftUp(arr_.size() - 1); } void pop() { if (arr_.empty()) throw std::runtime_error("empty heap"); swap(arr_[0], arr_.back()); arr_.pop_back(); siftDown(0); } int top() { if (arr_.empty()) throw std::runtime_error("empty heap"); return arr_[0]; } // 自底向上堆化 void heapify() { if (arr_.empty()) return; int last_leaf = arr_.size() - 1; int last_internal = (last_leaf - 1) / 2; for (int i = last_internal; i >= 0; --i) { siftDown(i); } } private: // 上滤: 给定叶节点 i,若其大于父节点,则与父节点交换,持续地上移; void siftUp(int i) { while (i > 0) { int parent = (i - 1) / 2; if (arr_[i] <= arr_[parent]) break; swap(arr_[i], arr_[parent]); i = parent; } } // 下滤: 给定内部节点 i,若其小于子节点,则与最大子节点交换,持续地下移. void siftDown(int i) { while (true) { int max_idx = i; int left = 2*i + 1, right = 2*i + 2; if (left < arr_.size() && arr_[left] > arr_[max_idx]) { max_idx = left; } if (right < arr_.size() && arr_[right] > arr_[max_idx]) { max_idx = right; } if (max_idx == i) break; swap(arr_[max_idx], arr_[i]); i = max_idx; } } private: vector<int> arr_; }; ``` <br><br> # 堆排序 **==堆排序==** 是基于 "堆" 这一数据结构的排序方式,分为两步: - **堆化**:调用 `heapify` 将原数组堆化,即**将数组元素按堆对应的逻辑方式进行排列**。 - **排序**:每次**将堆顶元素交换到 "剩余堆的末尾 i "**,将**末尾元素`i`从堆中排除**,并**对新的堆顶元素执行 `SiftDown` 下滤**。 整个过程相当于,**依次将==堆顶元素==弹出,==从后到前==地放置到数组之中,直至堆空**。 ![[Excalidraw-Solution/堆.excalidraw.md#^group=YPf3kyOLhBHhMRMwP3Z3R|600]] ### 复杂度分析 - 时间复杂度:$O(n + n\log n)$ 堆化以及 n 项元素出堆的开销; - 空间复杂度:$O(1)$ 堆排序无需额外的空间开销。 ### 堆排序的实现 ```cpp // 堆排序(大顶堆进行升序排序) class HeapSortImpl { public: static void sort(vector<int>& arr) { int n = arr.size(); heapify(arr); for (int i = n - 1; i > 0; --i) { swap(arr[0], arr[i]); // 每次将堆顶元素交换到"剩余堆的末尾下标i" siftDown(arr, 0, i); // 对新的堆顶元素进行下滤. 剩余堆的大小为i. } } private: static void heapify(vector<int>& arr) { if (arr.empty()) return; int last_leaf = arr.size() - 1; int last_internal = (last_leaf - 1) / 2; for (int i = last_internal; i >= 0; --i) { siftDown(arr, i, arr.size()); } } static void siftDown(vector<int>& arr, int i, int size) { while (true) { int max_idx = i; int left = 2*i + 1, right = 2*i + 2; if (left < size && arr[left] > arr[max_idx]) { max_idx = left; } if (right < size && arr[right] > arr[max_idx]) { max_idx = right; } if (max_idx == i) break; swap(arr[i], arr[max_idx]); i = max_idx; } } }; ``` # 堆的作用 - **小顶堆**:求最大的 k 项元素(堆顶为第 k 大的元素,作为门槛) - **大顶堆**:求最小的 k 项元素(堆顶为第 k 小的元素,作为门槛) --- <br><br> # C++ 标准库中的优先队列 > 由 `<queue>` 头文件提供 ### `priority_queue` 类模版声明 > ![[_attachment/00-数据结构与算法笔记/数据结构/堆(二叉堆).assets/IMG-堆(二叉堆)-BBF97F1EE9A07E34888331E9F63E9ADC.png|774]] ### 自定义比较函数 优先队列 `std::priority_queue` 以 `Compare(const T& a, const T& b)` 作为**比较函数**, **当 ==`Compare(a, b)`== 为 `true` 时,认为 ==`b` 的优先级较高==**,从而**将 `b` 排在队列==前方==(靠近堆顶)**。 ###### 例题 [[99-题解/leetcode/lc-692 前 K 个高频单词|lc-692 前 K 个高频单词]] # C++ 标准库中的堆相关算法 > 由 `<algorithm>` 头文件提供 - `std::make_heap()` - `std::push_heap()` - `std::pop_heap()` - `std::sort_heap()` <br> <br> # 参考资料 [8.1   堆 - Hello 算法](https://www.hello-algo.com/chapter_heap/heap/) # Footnotes