Leetcode_前k个高频元素

本文介绍了一种使用优先队列优化寻找数组中出现频率最高的Top K元素的算法。通过C++的优先队列实现了一个简洁高效的解决方案,并对比了传统的堆排序方法,展示了如何利用优先队列来提高算法效率。

思路大概是将数组中所有元素的频率记下来,然后统一使用最小堆来管理,将根节点与下一个元素比较,将频率更的高元素插入最小堆,同时进行堆排序操作,以此进行管理

在这里发现原来c++有优先队列priority_queue可以达到相同的效果,优先队列将权值最大的放在队列的出口处,这样就完成了类似的效果

原算法:

class Solution {

public:
    int sift_down(vector<pair<int,int>> &v,int k){
        int father=0;
        int child=2*father+1;
        pair<int,int> temp=v[0];
        while(child<k){
            if((child+1<k)&&(v[child].first>v[child+1].first))
                child++;
            if(temp.first>v[child].first){
                v[father]=v[child];
                father=child;
                child=father*2+1;
            }
            else
                break;
        }
        v[father]=temp;
        return 0;
    }
    
    vector<int> topKFrequent(vector<int>& nums, int k) {
        vector<pair<int,int>> v;
        for(int i=0;i<k;i++){
            v.push_back(make_pair(0,0));
        }
        map<int,int> m;
        for(vector<int>::iterator it=nums.begin();it!=nums.end();it++){
            if(m.find(*it)->second==0)
                m.insert(make_pair(*it,1));
            else
                m.find(*it)->second++;            
        }
        for(map<int,int>::iterator it=m.begin();it!=m.end();it++){
            if(it->second>v[0].first){
                v[0]=make_pair(it->second,it->first);
                sift_down(v,k);   
            }
        }
        vector<int> v1;
        for(vector<pair<int,int>>::iterator i=v.begin();i!=v.end();i++)
            v1.push_back(i->second);
        return v1;
    }
    

};


改进算法,写的很漂亮:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
         unordered_map<int,int> m;
        priority_queue<pair<int,int>> p;
        vector<int> res;
        for(int num:nums)
        {
            ++m[num];
        }
        for(auto a:m)
        {
            p.push({a.second,a.first});
        }
        for(int i=0;i<k;++i)
        {
            res.push_back(p.top().second);
            p.pop();
        }
        return res;
    }
};

<think>我们正在讨论C++ STL中的priority_queue,其底层实现原理。根据引用资料,priority_queue是一个优先级队列,它允许高效地获取最大或最小元素(默认为最大堆),并支持插入和删除操作。 底层实现原理: 1. 容器:priority_queue默认使用vector作为底层容器,用于存储元素。选择vector是因为它支持随机访问、高效的尾部插入和删除,并且可以通过下标计算来维护堆结构。 2. 堆算法:priority_queue在vector上应用堆算法(通常是二叉堆)来维护元素的顺序。具体来说,它使用以下堆操作: - 插入元素:将新元素添加到vector的末尾,然后执行上滤(sift up)操作,直到满足堆的性质。 - 删除堆顶元素:将堆顶元素(即vector的第一个元素)与最后一个元素交换,然后删除最后一个元素(即原堆顶),然后对新的堆顶元素执行下滤(sift down)操作,直到满足堆的性质。 - 获取堆顶元素:直接返回vector的第一个元素。 3. 堆的类型:默认情况下,priority_queue是一个最大堆(即堆顶元素为最大值)。用户也可以通过指定比较函数来创建最小堆。 4. 时间复杂度: - 获取堆顶元素:$O(1)$ - 插入元素:$O(\log n)$ - 删除堆顶元素:$O(\log n)$ 5. 自定义比较:priority_queue模板有三个参数: - 元素类型(T) - 底层容器类型(默认为vector<T>) - 比较函数对象(默认为less<T>,即最大堆) 例如,要创建最小堆,可以这样声明: ```cpp priority_queue<int, vector<int>, greater<int>> minHeap; ``` 6. 不支持随机访问:priority_queue只允许访问堆顶元素,不允许访问中间元素。 引用说明: 根据引用[1]:priority_queue默认使用vector作为底层容器,并使用堆算法构造堆结构,默认是大堆。 引用[2]指出:堆的本质是一颗完全二叉树,而vector通过下标映射节点父子关系,非常适合作为堆的底层容器。 引用[3]提到:priority_queue提供了常数时间获取最大(或最小)元素的能力,同时保证了高效的元素插入和删除。 下面是一个简化的priority_queue实现示例,展示其核心操作: ```cpp #include <vector> #include <functional> // for less template <typename T, typename Container = std::vector<T>, typename Compare = std::less<T>> class priority_queue { private: Container c; // 底层容器 Compare comp; // 比较函数对象 // 上滤操作 void sift_up(int index) { while (index > 0) { int parent = (index - 1) / 2; if (comp(c[parent], c[index])) { std::swap(c[parent], c[index]); index = parent; } else { break; } } } // 下滤操作 void sift_down(int index) { int n = c.size(); while (index < n) { int left = 2 * index + 1; int right = 2 * index + 2; int largest = index; if (left < n && comp(c[largest], c[left])) { largest = left; } if (right < n && comp(c[largest], c[right])) { largest = right; } if (largest != index) { std::swap(c[index], c[largest]); index = largest; } else { break; } } } public: priority_queue() = default; void push(const T& value) { c.push_back(value); sift_up(c.size() - 1); } void pop() { if (c.empty()) return; std::swap(c[0], c.back()); c.pop_back(); sift_down(0); } const T& top() const { return c.front(); } bool empty() const { return c.empty(); } size_t size() const { return c.size(); } }; ``` 在这个实现中,我们使用了模板来支持泛型,并允许自定义容器和比较函数。注意,比较函数默认是`std::less<T>`,它构建的是最大堆(因为比较函数在判断父节点是否小于子节点时,如果为真则交换,所以较大的元素会上升)。如果使用`std::greater<T>`,则构建最小堆。 总结:priority_queue的底层实现是基于vector容器和堆算法(上滤和下滤)来维护一个完全二叉树(堆)的结构,从而保证高效的操作性能。 §§相关问题§§ 1. priority_queue的底层容器可以更换吗?比如使用deque? 2. 如何利用priority_queue实现一个最小堆? 3. priority_queue与set/map在性能和应用场景上有何不同? 4. 在priority_queue中,如何自定义比较函数来处理复杂类型?</think>### priority_queue底层实现原理 priority_queue(优先队列)的底层实现原理基于以下核心设计: 1. **数据结构基础** priority_queue本质是一个**堆(Heap)**,具体来说是**完全二叉树**结构。其底层容器默认使用**vector**实现[^1][^2]。这种设计充分利用了vector的连续内存特性: - 通过下标计算父子节点关系: 父节点索引:$parent(i) = \lfloor (i-1)/2 \rfloor$ 左子节点:$left(i) = 2i + 1$ 右子节点:$right(i) = 2i + 2$ - 支持$O(1)$随机访问,保证堆调整效率 2. **堆算法实现** 通过堆算法维护元素顺序: ```cpp // 上浮操作(插入元素时) void sift_up(int index) { while (index > 0) { int parent = (index - 1) / 2; if (comp(container[parent], container[index])) { swap(container[parent], container[index]); index = parent; } else break; } } // 下沉操作(删除堆顶时) void sift_down(int index) { int child = 2 * index + 1; while (child < size()) { if (child + 1 < size() && comp(container[child], container[child+1])) child++; // 选择更大/小的子节点 if (comp(container[index], container[child])) { swap(container[index], container[child]); index = child; child = 2 * index + 1; } else break; } } ``` 3. **核心操作复杂度** | 操作 | 时间复杂度 | 实现原理 | |------------|------------|------------------------------| | `push()` | $O(\log n)$ | 尾部插入 + 上浮调整 | | `pop()` | $O(\log n)$ | 首尾交换 + 删除 + 下沉调整 | | `top()` | $O(1)$ | 直接返回首元素 | 4. **比较器控制堆类型** 通过模板参数决定堆性质: ```cpp template <class T, class Container = vector<T>, class Compare = less<T>> // 默认大顶堆 class priority_queue { Compare comp; // 比较函数对象 // ... }; ``` - 大顶堆:`less<T>` → 父节点 $\geq$ 子节点 - 小顶堆:`greater<T>` → 父节点 $\leq$ 子节点 5. **容器适配器特性** priority_queue是**容器适配器**而非独立容器: - 基于现有容器(默认vector)构建 - 仅开放堆操作接口,隐藏底层容器的其他接口 - 依赖底层容器的`push_back()`, `pop_back()`, `front()`等方法 ### 内存布局示例 ``` vector存储: [16, 14, 10, 8, 7, 9, 3, 2, 4, 1] 对应的完全二叉树: 16 / \ 14 10 / \ / \ 8 7 9 3 / \ 2 4 / 1 ``` ### 设计优势 1. **空间效率**:vector连续存储避免指针开销 2. **缓存友好**:连续内存提高CPU缓存命中率 3. **操作优化**:堆算法保证核心操作对数复杂度 4. **灵活性**:可通过自定义比较器实现复杂优先级规则
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值