优先队列的使用
一.堆(优先队列)
在物理上可以用向量以及数组实现,这里使用优先队列来实现,而优先队列可以在物理上实现树,这里用来实现哈夫曼树(由优先队列的特点来确定使用它来实现的)。
二.用堆解决的数据处理的问题
如果问题提到了数据的权重问题,即数据某种情况的出现次数,那么我们可以考虑把数据处理成树结构来处理(这里使用哈夫曼树来处理),然后用数组或者队列来实现(这里使用优先队列来实现)。
例如下面这道例题:
重编码问题
问题描述
有一篇文章,文章包含 n 种单词,单词的编号从 1 至 n,第 i 种单词的出现次数为 w[i]。
现在,我们要用一个 2 进制串(即只包含 0 或 1 的串) s[i] 来替换第 i 种单词,使其满足如下要求:对于任意的 1≤i,j≤n(i≤j),都有 s[i] 不是 s[j] 的前缀。(这个要求是为了避免二义性)
你的任务是对每个单词选择合适的 s[i],使得替换后的文章总长度(定义为所有单词出现次数与替换它的二进制串的长度乘积的总和)最小(二进制串的长度就是权值路径长度)。求这个最小长度。
字符串 S1(不妨假设长度为 n)被称为字符串 S2 的前缀,当且仅当:S2 的长度不小于 n,且 S1 与 S2 前 n 个字符组组成的字符串完全相同。
输入格式
第一行一个整数 n,表示单词种数。
第 2 行到第 n+1 行,第 i+1 行包含一个正整数 w[i],表示第 i 种单词的出现次数。
输出格式
输出一行一个整数,表示整篇文章重编码后的最短长度。
样例输入
4
1
1
2
2
样例输出
12
实现代码:
#include <bits/stdC++.h> using namespace std; typedef long long ll; priority_queue<ll,vector<ll>,greater<ll> > pq; // n:题目描述中的n // 返回值:答案 ll Haffman(int n, vector<ll> w) { // 将所有w加入优先队列pq中 for(int i = 0; i < n; ++i) { pq.push(w[i]); } ll sum = 0; // 置零返回值 while (pq.size() > 1) { // 当pq中仍有超过多少个元素时进行循环呢? ll newEle = 0; // 这是本次合并后将要加入队列的新元素 // 从pq中取出最小的2个元素合并 // 下面的for循环起到一个累加的作用 for(int k = 0; k < 2; ++k) { newEle = newEle + pq.top(); // 取最小的元素相加 pq.pop(); // 使用完之后,把队列首个已经加过的元素弹出 } sum += newEle; // 将本次合并对答案的贡献加入答案(新得到树的根) pq.push(newEle); // 将新元素加入优先队列 } return sum; // 返回答案 } int main(int argc, char const *argv[]) { int N; scanf("%d",&N); vector<ll> w; for(int i = 0;i < N;i++){ int v; scanf("%d",&v); w.push_back(v); } printf("%lld",Haffman(N,w)); return 0; }
三.优先队列的编码实现分析
1.如果使用C++来实现,会有封装好的priority_queue类直接使用。
2.问题要求所有单词出现次数与替换它的二进制串的长度乘积的总和最小,单词出现次数是一定无法改变的,我们可以通过找替换它二进制串长度最小的情况来实现它,哈夫曼树的特点既可以完成,所以在逻辑上我们可以考虑把数据组织成哈夫曼树的形式。
3.确定了哈夫曼树之后,我们需要构造哈夫曼树,构造哈夫曼树的算法就是先取出最小的两个数。
4.在哈夫曼树上求权值路径长度和的算法见iPad上的个人笔记,分别有两种算法,一种是要构造出树再计算出权值综合,另外一种是不需要维护树即可进行权值综合的计算。