C++ 堆与优先级队列

"learning……"

Posted by Ryan on January 9, 2025

魔非魔,道非导,善恶在人心。欲非欲,情非情,因缘由天定。

堆是一个完全二叉树结构,但是它却存储在一个数组里面。所谓完全二叉树,即这棵树的元素是从左到右从上到下填充的,每一层都是连续存在的。
将这颗完全二叉树从左到右从上到下填入数组,我们可以发现:一个结点为i的父节点,其子节点下标为2i+1和2i+2;对于一个下标为i的子结点,其父节点为(i-1)/2,由于整形会自动向下取整,所以不论是左子节点还是右子节点,都可以以这个代码得到父节点。
如果堆每个节点都大于等于它的所有孩子,那么这个堆就是最大堆;如果每个节点都小于等于它的所有孩子,那么这个堆就是最小堆。

image

自定义堆

堆的操作有建堆,堆化(堆下沉),pop

首先是最核心的堆化操作(这里以最大堆下沉为例):检查父节点是否比所有子节点大,否的话父节点与最大子节点交换,一直重复到堆的最下方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void maxheapdown(vector<int>&nums,int i)
{
    int left=2*i+1;
    int right=2*i+2;
    int largest=i;
    if(left<nums.size()&&nums[largest]<nums[left])
    {
        largest=left;
    }
    if(right<nums.size()&&nums[largest]<nums[right])
    {
        largest=right;
    }
    if(largest!=i) 
    {
        swap(nums[largest],nums[i]);
        maxheapdown(nums,largest);
    }
}

建堆的操作就是对所有的父节点进行检查和堆化

1
2
3
4
5
6
7
void makeheap(vector<int>& nums)
{
    for(int i=nums.size()/2-1;i>=0;i--)
    {
        maxheapdown(nums,i);
    }
}

出堆的操作就是交换堆顶到最下方(数组头和尾互换),然后排除数组尾,对堆顶进行一次堆化操作

1
2
3
4
5
6
void heappop(vector<int>& nums)
{
    swap(nums[0],nums[nums.size()-1]);
    nums.pop_back();
    maxheapdown(nums,0);
}

STL的优先级队列使用方法

priority_queue 是一个容器适配器,默认使用最大堆。如果需要最小堆,可以通过传入一个比较器来改变堆的性质。

默认最大堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <queue>

using namespace std;

int main() {
    // 创建一个最大堆
    priority_queue<int> maxHeap;

    // 插入元素
    maxHeap.push(10);
    maxHeap.push(20);
    maxHeap.push(15);

    // 输出堆顶元素(最大元素)
    cout << "最大堆的堆顶元素: " << maxHeap.top() << endl; // 输出: 20

    // 删除堆顶元素
    maxHeap.pop();
    cout << "删除堆顶元素后的堆顶元素: " << maxHeap.top() << endl; // 输出: 15

    return 0;
}

最小堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

int main() {
    // 创建一个最小堆
    priority_queue<int, vector<int>, greater<int>> minHeap;

    // 插入元素
    minHeap.push(10);
    minHeap.push(20);
    minHeap.push(15);

    // 输出堆顶元素(最小元素)
    cout << "最小堆的堆顶元素: " << minHeap.top() << endl; // 输出: 10

    // 删除堆顶元素
    minHeap.pop();
    cout << "删除堆顶元素后的堆顶元素: " << minHeap.top() << endl; // 输出: 15

    return 0;
}

配合vector使用的堆操作make_heap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    vector<int> arr = {10, 20, 15, 30, 40};

    // 创建最大堆
    make_heap(arr.begin(), arr.end());

    // 输出堆顶元素
    cout << "最大堆的堆顶元素: " << arr.front() << endl; // 输出: 40

    // 添加元素
    arr.push_back(50);
    push_heap(arr.begin(), arr.end()); // 将新元素添加到堆中

    cout << "添加元素后堆顶元素: " << arr.front() << endl; // 输出: 50

    // 移除堆顶元素
    pop_heap(arr.begin(), arr.end());
    arr.pop_back();

    cout << "移除堆顶元素后堆顶元素: " << arr.front() << endl; // 输出: 40

    return 0;
}