树-堆结构练习——合并果子之哈夫曼树

果园合并问题

题目描述

 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所消耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
 

输入

 第一行是一个整数n(1<=n<=10000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个ai(1<=ai<=20000)是第i个果子的数目。
 

输出

 输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于2^31。
 

示例输入

3
1 2 9

示例输出

15


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
int main()
{
    priority_queue<int,vector<int>,greater<int> >q;//优先队列排序,升序;
    int n,num,sum=0;//统计最小的体力耗费值;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&num);
        q.push(num);
    }
    while(!q.empty())
    {
        int x=q.top();
        q.pop();
        if(!q.empty())
        {
            int y=q.top();
            q.pop();
            int z=x+y;//两堆果子合并所花费的最小体力;
            q.push(z);
            sum+=z;
        }
    }
    printf("%d\n",sum);
    return 0;
}

<think> 我们正在讨论C++中的priority_queue(优先队列)。根据引用内容,priority_queue是一种容器适配器,它提供了一种按优先级而不是插入顺序获取元素的机制。它的底层实现通常是二叉,默认使用vector作为底层容器,并且默认是最大(通过std::less<T>实现,但实际上最大是根节点最大的)。 我将按照以下结构详细讲解: 1. 定义与特性 2. 底层实现与原理(二叉) 3. 常用操作(push, pop, top, empty, size) 4. 自定义比较函数(如何实现最小或其他排序) 5. 底层容器的选择及注意事项 6. 应用场景 7. 性能分析 8. 线程安全问题 9. 自实现priority_queue(二叉)的简单示例 注意:由于用户要求详细全面,我们将尽可能覆盖以上方面。 1. 定义与特性 优先级队列(priority_queue)是一种容器适配器,它允许用户以优先级顺序检索元素。每次从队列中取出的元素都是当前优先级最高的元素。它不支持随机访问,也不提供迭代器。 特性: - 默认情况下,元素按从大到小排序(最大),即队首元素(top)是最大值。 - 底层通常由(heap)数据结构实现,是一棵完全二叉,可以用数组(vector)来存储。 - 插入和删除元素的时间复杂度为$O(\log n)$,访问队首元素的时间复杂度为$O(1)$。 - 它是一个容器适配器,封装了底层的容器(默认是vector),并提供了操作。 2. 底层实现与原理:二叉 二叉是一种完全二叉,可以分为最大和最小- 最大:每个节点的值都大于或等于其子节点的值。根节点是最大值。 - 最小:每个节点的值都小于或等于其子节点的值。根节点是最小值。 二叉通常使用数组(或vector)来表示。对于给定位置$i$的节点: - 其父节点位置:$\lfloor (i-1)/2 \rfloor$ - 左子节点位置:$2i+1$ - 右子节点位置:$2i+2$ 的操作: - 向上调整(sift up / percolate up):当在尾插入新元素时,需要向上调整以保持的性质。 - 向下调整(sift down / percolate down):当删除顶元素(或替换顶元素)时,需要将尾元素移到顶,然后向下调整以保持的性质。 3. 常用操作 在C++中,priority_queue包含在<queue>头文件中。常用操作有: - push(element): 插入一个元素,并调整(向上调整),时间复杂度$O(\log n)$。 - pop(): 移除队首元素(即顶),并调整(向下调整),时间复杂度$O(\log n)$。 - top(): 返回队首元素(顶元素),不删除,时间复杂度$O(1)$。 - empty(): 判断队列是否为空,时间复杂度$O(1)$。 - size(): 返回队列中元素个数,时间复杂度$O(1)$。 4. 自定义比较函数 默认情况下,priority_queue是最大(队首最大)。我们可以通过自定义比较函数来改变优先级顺序。 例如,实现最小(队首最小): ```cpp // 最小 priority_queue<int, vector<int>, greater<int>> min_heap; ``` 自定义比较函数(例如,自定义结构体): ```cpp struct Node { int value; // 其他字段... }; struct Compare { bool operator()(const Node& a, const Node& b) { // 返回true表示a的优先级低于b(即a排在b的后面) return a.value < b.value; // 这样是最大(按照value从大到小) // 如果 return a.value > b.value; 则是最小 } }; priority_queue<Node, vector<Node>, Compare> custom_pq; ``` 5. 底层容器的选择及注意事项 priority_queue的模板声明如下: ```cpp template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> > class priority_queue; ``` 底层容器必须满足序列容器的要求,且提供随机访问迭代器(如vector或deque)。注意: - 默认容器是vector。 - 也可以使用deque,但vector通常性能更好(因为连续内存)。 - 不能使用list(因为不支持随机访问,调整需要随机访问)。 6. 应用场景 - 任务调度:操作系统中的优先级调度算法。 - 图算法:如Dijkstra最短路径算法、Prim最小生成算法中,用于高效获取下一个待处理的节点。 - 数据流处理:处理不断到来的数据,只关心当前最大/最小的几个元素(如Top K问题)。 - 合并多个有序序列(排序)。 7. 性能分析 -:从一个无序序列建,可以使用自底向上的方法,时间复杂度$O(n)$(通过弗洛伊德算法)。 - 插入和删除:每次调整的时间复杂度为$O(\log n)$。 - 访问队首元素:$O(1)$。 - 注意:std::priority_queue不支持查找和修改任意元素的操作,如果确实需要,可能需要考虑其他数据结构(如斐波那契等,但C++标准库未提供)。 8. 线程安全问题 - std::priority_queue本身不是线程安全的。如果多个线程同时访问同一个priority_queue对象,并且至少有一个线程修改它,那么需要外部同步机制(如互斥锁)。 9. 自实现priority_queue(二叉)的简单示例 下面是一个简化的自实现最大(基于vector): ```cpp template <typename T, typename Compare = std::less<T>> class priority_queue { private: std::vector<T> data; Compare comp; // 向上调整 void sift_up(int index) { while (index > 0) { int parent = (index - 1) / 2; if (comp(data[parent], data[index])) { // 如果父节点小于当前节点(对于最大,需要交换) std::swap(data[parent], data[index]); index = parent; } else { break; } } } // 向下调整 void sift_down(int index) { int n = data.size(); while (index < n) { int left = 2 * index + 1; int right = 2 * index + 2; int largest = index; if (left < n && comp(data[largest], data[left])) largest = left; if (right < n && comp(data[largest], data[right])) largest = right; if (largest != index) { std::swap(data[index], data[largest]); index = largest; } else { break; } } } public: priority_queue() = default; void push(const T& value) { data.push_back(value); sift_up(data.size() - 1); } void pop() { if (data.empty()) return; std::swap(data[0], data.back()); data.pop_back(); sift_down(0); } const T& top() const { return data.front(); } bool empty() const { return data.empty(); } size_t size() const { return data.size(); } }; ``` 注意:上述自实现代码是简化版,实际标准库的实现可能更复杂(比如异常安全等)。 总结:priority_queue是一种高效的优先级队列,适用于需要按优先级处理元素的场景。通过自定义比较函数和底层容器,可以适应不同的需求。但在使用时需要注意其局限性(如不支持随机访问、非线程安全等)[^1][^2][^3][^4]。 如果您有任何具体问题,可以进一步提问。 §§相关问题§§ 1. priority_queue在Dijkstra算法中是如何应用的? 2. 如何使用priority_queue解决Top K问题? 3. priority_queue与set/multiset在功能上有何异同? 4. 如何实现一个线程安全的priority_queue? 5. 为什么priority_queue默认使用vector作为底层容器? </think> ### Priority Queue(优先队列)的全面讲解 #### 1. **基本定义与特性** - **核心概念** Priority Queue(优先队列)是一种抽象数据类型,元素按**优先级顺序**而非插入顺序出队。每次出队的元素是当前队列中优先级最高(或最低)的元素[^1][^4]。 - **关键特性**: - 默认实现为**最大**(根节点为最大值),可通过自定义比较器调整为最小[^2] - 不支持随机访问和迭代器操作[^2][^3] - 时间复杂度: - 插入(push):$O(\log n)$ - 删除队首(pop):$O(\log n)$ - 访问队首(top):$O(1)$ #### 2. **底层实现原理** - **数据结构**: 基于**二叉**(完全二叉),通常用数组(如`std::vector`)存储[^3][^4]: ```plaintext 16 / \ 14 10 / \ / 8 7 9 // 数组存储:[16, 14, 10, 8, 7, 9] ``` - **核心操作**: - **向上调整(Sift Up)**:插入元素时,从底部向上调整堆结构 - **向下调整(Sift Down)**:删除根节点时,将末尾元素移至根部并向下调整[^3] - **数学关系**(对于节点$i$): - 父节点索引:$\lfloor (i-1)/2 \rfloor$ - 左子节点索引:$2i+1$ - 右子节点索引:$2i+2$ #### 3. **C++标准库实现(std::priority_queue)** ```cpp #include <queue> #include <vector> #include <functional> // 默认最大 std::priority_queue<int> max_heap; // 最小定义 std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap; // 自定义比较器 struct Node { int value; }; auto cmp = [](const Node& a, const Node& b) { return a.value < b.value; // 最大规则 }; std::priority_queue<Node, std::vector<Node>, decltype(cmp)> custom_pq(cmp); ``` #### 4. **核心操作详解** | **操作** | **说明** | **时间复杂度** | |----------------|-----------------------------------|---------------| | `push(val)` | 插入元素并调整 | $O(\log n)$ | | `pop()` | 删除队首元素 | $O(\log n)$ | | `top()` | 访问队首元素(不删除) | $O(1)$ | | `empty()` | 判断队列是否为空 | $O(1)$ | | `size()` | 返回元素数量 | $O(1)$ | #### 5. **应用场景** 1. **任务调度** 操作系统按优先级处理任务(如实时系统)[^1] 2. **图算法** - Dijkstra最短路径算法 - Prim最小生成算法 3. **数据流处理** - Top K问题:维护大小为K的最小找最大K元素 ```cpp // 找数组中最大的K个元素 std::priority_queue<int, std::vector<int>, std::greater<int>> pq; for (int num : nums) { pq.push(num); if (pq.size() > k) pq.pop(); } ``` 4. **离散事件模拟** 按时间戳优先级处理事件 #### 6. **性能与注意事项** - **容器选择**: 默认`std::vector`(连续内存高效),也可用`std::deque`,但禁用`std::list`(不支持随机访问)[^2][^4] - **线程安全**: 标准库实现**非线程安全**,多线程需外部同步[^2] - **建优化**: 批量建时间复杂度$O(n)$(弗洛伊德算法),优于单次插入$O(n\log n)$ ```cpp std::vector<int> vec{3,1,4,2,5}; std::priority_queue<int> pq(vec.begin(), vec.end()); // O(n)建 ``` #### 7. **与其他数据结构对比** | **数据结构** | **优势** | **劣势** | |--------------------|-----------------------------------|------------------------| | Priority Queue | 高效优先级访问($O(1)$ top) | 不支持随机访问 | | std::set | 自动排序+快速查找($O(\log n)$) | 内存开销大(红黑) | | std::deque | 支持双端操作 | 无内置优先级管理 | #### 8. **自实现示例(最大)** ```cpp template<typename T, typename Compare = std::less<T>> class PriorityQueue { std::vector<T> heap; Compare comp; void sift_up(int i) { while (i > 0) { int parent = (i-1)/2; if (comp(heap[parent], heap[i])) { std::swap(heap[parent], heap[i]); i = parent; } else break; } } void sift_down(int i) { int n = heap.size(); while (true) { int left = 2*i+1, right = 2*i+2, max = i; if (left < n && comp(heap[max], heap[left])) max = left; if (right < n && comp(heap[max], heap[right])) max = right; if (max == i) break; std::swap(heap[i], heap[max]); i = max; } } public: void push(const T& val) { heap.push_back(val); sift_up(heap.size()-1); } void pop() { heap[0] = heap.back(); heap.pop_back(); sift_down(0); } const T& top() const { return heap[0]; } bool empty() const { return heap.empty(); } }; ``` ### 总结 Priority Queue 通过二叉实现高效的优先级管理,在任务调度、图算法等场景至关重要。使用时需注意: 1. 默认最大,可通过比较器自定义优先级规则 2. 选择`std::vector`作为底层容器以获得最佳性能 3. 不支持随机访问,多线程需同步 4. 优先使用批量建($O(n)$)而非单元素插入[^1][^2][^3][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值