手写priority_queue


priority_queue 的底层是堆。

C++标准库中 直接使用 std::make_heap, std::push_heap, std::pop_heap 来实现 priority_queue ,代码如下:

#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::make_heap, std::push_heap, std::pop_heap

template <typename T, typename Container = std::vector<T>>
class MyPriorityQueue {
private:
    Container data; // 使用底层容器存储优先队列的元素

public:
    // 默认构造函数
    MyPriorityQueue() {}

    // 构造函数,可以指定底层容器
    MyPriorityQueue(const Container& c) : data(c) {
        std::make_heap(data.begin(), data.end());
    }

    // 将元素添加到优先队列中
    void push(const T& value) {
        data.push_back(value);
        std::push_heap(data.begin(), data.end());
    }

    // 弹出优先队列中的最大元素
    void pop() {
        if (!empty()) {
            std::pop_heap(data.begin(), data.end());
            data.pop_back();
        } else {
            throw std::runtime_error("Priority queue is empty.");
        }
    }

    // 访问优先队列中的最大元素
    T& top() {
        if (!empty()) {
            return data.front();
        } else {
            throw std::runtime_error("Priority queue is empty.");
        }
    }

    // 检查优先队列是否为空
    bool empty() const {
        return data.empty();
    }

    // 返回优先队列的大小
    size_t size() const {
        return data.size();
    }
};

int main() {
    // 使用默认底层容器(std::vector)的示例
    MyPriorityQueue<int> pq1;

    pq1.push(3);
    pq1.push(1);
    pq1.push(4);
    pq1.push(1);

    std::cout << "Top element of pq1: " << pq1.top() << std::endl;

    pq1.pop();
    std::cout << "Priority queue pq1 size after pop: " << pq1.size() << std::endl;

    // 使用 std::vector 作为底层容器的示例
    std::vector<int> vec = {9, 5, 7, 2, 6};
    MyPriorityQueue<int, std::vector<int>> pq2(vec);

    std::cout << "Top element of pq2: " << pq2.top() << std::endl;

    pq2.pop();
    std::cout << "Priority queue pq2 size after pop: " << pq2.size() << std::endl;

    return 0;
}

堆的工作原理

堆(Heap)是一种特殊的完全二叉树,它满足下面的性质:

结构性质: 堆是一个完全二叉树,这意味着除了最后一层外,每一层都是完全填满的,而最后一层的节点则尽可能地集中在左边。

堆性质: 在一个最大堆(Max Heap)中,每个节点的值都大于或等于其子节点的值,根节点的值是堆中的最大值。相反,在一个最小堆(Min Heap)中,每个节点的值都小于或等于其子节点的值,根节点的值是堆中的最小值。

堆的插入和删除

插入操作(Insertion): 插入新元素时,新元素首先被放置在树的最后一个位置,以保持完全二叉树的结构。然后,该元素会通过一个称为“上浮”(或“堆化”)的过程,与其父节点比较并交换位置(如果在最大堆中新元素比父节点大,或在最小堆中新元素比父节点小)。这个过程重复进行,直到新元素到达一个位置,它不再比父节点大(或小,取决于是最大堆还是最小堆),或者它已经到达了树的顶部。

删除操作(Deletion): 在堆中,删除操作通常指的是删除根节点,即最大元素或最小元素。删除后,堆的结构性质必须得到维护。这通常通过将最后一个元素移到根节点的位置来完成,接着执行“下沉”(或“堆化”)过程,该元素会与其子节点比较并根据需要与较大(或较小)的子节点交换位置。这个过程持续进行,直到该元素位于正确的位置,或者它已经到达了树的底部。

构建堆(Heapify): 从无序数组构建堆的过程称为堆化(Heapify)。这可以通过从最后一个非叶子节点开始,对每个节点执行下沉操作来完成。在数组中,给定索引为i的元素,其左子节点的索引为2*i + 1,右子节点的索引为2*i + 2,父节点的索引为(i-1)/2。

如何用数组表示堆?

图解中的堆都是小根堆

在这里插入图片描述

上面的图片展示了对和数组的对应关系, 假定数组索引从1开始, 每个堆节点对应的数组索引用黑色方框在下面标注了, 可以看到, 堆的每个节点与数组之前的对应关系为:

给定索引为i的元素,其左子节点的索引为2*i + 1,右子节点的索引为2*i + 2,父节点的索引为(i-1)/2, 其父节点的索引为i//2

图解: 堆的插入

在这里插入图片描述

上面的图片展示了堆的插入过程, 蓝色节点表示新插入的元素, 首先将其插入在堆底, 然后和父节点进行比较, 如果新的节点更小, 就交换父子节点, 一直递归地进行直到根节点或父节点小于子节点

图解: 堆顶的删除

在这里插入图片描述

上面的图片展示了堆顶的删除过程, 删除时用堆底节点进行替换, 然后将根顶节点逐步下游, 注意下游时要选择更小的孩子进行交换

如果我们想自己实现堆,不用标准库里的堆函数,可以这么写,相对复杂一些(就是自己实现堆)

题目描述

在这里插入图片描述

C++ 代码实现

#include <iostream>
#include <vector>
#include <deque>
#include <stdexcept>
#include <sstream>
#include <string>

using namespace std;

template <typename T, typename Container = std::vector<T>>
class MyPriorityQueue {
private:
    Container data;
    
    // 辅助函数:向上调整元素以维护堆性质
    void heapifyUp() {
        int idx = data.size() - 1;
        while(idx > 0) {
            int parentIdx = (idx - 1) / 2;
            if(data[idx] > data[parentIdx]) {
                swap(data[idx], data[parentIdx]);
                idx = parentIdx;
            } else {
                break;
            }
        }
    }
    
    // 辅助函数:向下调整元素以维护堆性质
    void heapifyDown(Container& heap, int low, int high) {
        int i = low;
        int j = 2 * i + 1;
        while(j <= high) {
            if(j + 1 <= high && heap[j + 1] > heap[j]) {
                j = j + 1;
            }
            if(heap[j] > heap[i]) {
                swap(heap[j], heap[i]);
                i = j;
                j = 2 * i + 1;
            } else {
                break;
            }
        }
    }
    
public:
    MyPriorityQueue() {}
    
    MyPriorityQueue(const Container& c) : data(c) {
        int n = data.size();
        for(int i = (n / 2) - 1; i >= 0; --i) {
            heapifyDown(data, i, n - 1);
        }
    }
    
    void push(const T& value) {
        data.push_back(value);
        heapifyUp();
    }
    
    void pop() {
        int n = data.size();
        if(!empty()) {
            swap(data[0], data[n - 1]);
            data.pop_back();
            heapifyDown(data, 0, n - 2);
        } else {
            throw std::runtime_error("Priority queue is empty.");
        }
    }
    
    T& top() {
        if(!empty()) {
            return data[0];
        } else {
            throw std::runtime_error("Priority queue is empty.");
        }
    }
    
    bool empty() const {
        return data.empty();
    }
    
    size_t size() const {
        return data.size();
    }
};

int main() {
    // 使用 std::vector 作为底层容器
    MyPriorityQueue<int, std::vector<int>> myPriorityQueue;
    int N;
    cin>> N;
    getchar();
    string line;
    for(int i = 0; i < N; i++) {
        getline(cin, line);
        istringstream iss(line);
        string command;
        iss >> command;
        
        int element;
        
        if(command == "push") {
            iss >> element;
            myPriorityQueue.push(element);
        }
        
        if(command == "pop") {
            try{
                myPriorityQueue.pop();
            } catch(const runtime_error& e) {
                continue;
            }
        }
        
        if (command == "top") {
            try {
                std::cout << myPriorityQueue.top() << std::endl;
            } catch(const std::runtime_error& e) {
                std::cout << "null" << std::endl;
            }   
        }

        if (command == "size") {
            std::cout << myPriorityQueue.size() << std::endl;
        }

        if (command == "empty") {
            std::cout << (myPriorityQueue.empty() ? "true" : "false") << std::endl;
        }
    }
    return 0;
}

与C++标准库的区别:

STL中的std::priority_queue默认使用std::vector作为底层容器。这使得代码更加灵活,可以选择不同的容器类型。

代码中没有使用STL的std::make_heap、std::push_heap和std::pop_heap等堆算法来维护堆性质。相反,它使用了手动实现的堆操作函数heapifyUp和heapifyDown来维护堆。

代码中使用了自定义的异常处理来处理空队列的情况,而STL中的std::priority_queue可能会抛出std::out_of_range异常。

高频面试题

如何从给定的无序数组中找到第 K 大的元素?

在这里插入图片描述
215. 数组中的第K个最大元素【中等】(priority_queue)

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<>> minHeap;
        for(auto num: nums) {
            minHeap.push(num);
            if(minHeap.size() > k) {
                minHeap.pop();
            }
        }
        return minHeap.top();
    }
};

实现一个优先队列,并解释它的插入和删除操作的时间复杂度。

在这里插入图片描述

什么是堆排序?它的时间复杂度和空间复杂度是多少?

在这里插入图片描述

二叉堆和斐波那契堆有什么区别?它们的操作的时间复杂度有何不同?

在这里插入图片描述

如何在 O(n) 时间复杂度内构建一个堆?

在这里插入图片描述

解释堆排序算法以及它的时间复杂度。

在这里插入图片描述

给定一个 k 有序数组,如何在最优时间复杂度内对它进行排序?

在这里插入图片描述

在这里插入图片描述

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值