Huffman coding —— 优先队列

以一道题引入:
这里写图片描述

题目给出的样例和提示:
这里写图片描述

总结下题意:
利用哈夫曼编码给各个字符编码,求出最小的总字节数,即每个字符的编码数乘以字符的权重(这里是指字符出现的次数),得到每个字符的字节数,再对所有字符的字节数求和。

思路:
哈夫曼编码每次都是取权值最小的两个合并,因此每次都需要排序,这可以用到优先队列(priority_queue)。
提到哈夫曼编码,可能最先想到的最建一棵树,然后算出每个叶子的深度以得到每个字符的字节数,从而求出总字节数,但是针对这一题,这样做似乎复杂了。
可以这样想:一个叶子所在的树每被合并一次,叶子的深度就要加一,这个叶子的权重也要被加一次。因此,在优先队列中将权值最小结点的两个合并成一个新的结点,使新结点的权值等于两个被合并的结点的权值之和,并删除这两个结点。虽说这两个结点被删除了,但是实际上它们的权值以和的形式保留了下来。每次合并,都在现在总字节数的基础上加上新结点的权重,这样实际上就是对旧结点上所有里子结点的权值加了一次,最终得到的结果就是总字节数。

代码如下:

#include <iostream>
#include <cstdio>  
#include <queue>  
using namespace std;  

struct Node {  
    int weight;
    friend bool operator <(Node a, Node b) {     
        return a.weight > b.weight;  
        //思考下这里为什么用'>'而不用'<'(后面解释)
    }  
};  

main() {      
    int n, k;  
    cin >> n;
    Node left, right;  
    priority_queue <Node> q;    
    int sum = 0;  
    int m = n;  
    while(m--) {  
        char ch;
        Node x;
        cin >> ch >> x.weight;  
        q.push(x);  
    }  
    while(q.size() != 1) { //q.size()=1时表明已处理完 
        //下面依次取出最小的两个结点
        left = q.top();  
        q.pop();  
        right = q.top();  
        q.pop();   
        left.weight = left.weight + right.weight;   
        sum += left.weight;  //每合并一次就要将结点的权值加一次
        q.push(left);  //要记得把新的结点加进队列
    }  
    printf("%d\n", sum); 
    return 0;  
}  

看完了这道题,我们来了解一下优先队列的用法。

优先队列是队列的一种,不过它可以按照自定义的一种方式(数据的优先级)来对队列中的数据进行动态的排序。每次的push和pop操作,队列都会动态的调整,以达到我们预期的方式来存储。

priority_queue<Type, Container<Type>, Functional>

其中 Type 为数据类型, Container 为保存数据的容器,Functional 为元素比较方式。
Container 必须是用数组实现的容器,比如 vector, deque 但不能用 list,STL里面默认用的是 vector. 比较方式默认用 operator< , 所以如果把后面俩个参数缺省的话,优先队列就是大顶堆,队头元素最大。

例:

// q 是数据类型为 int 型的队列,队头元素[q.top()]最大
priority_queue<int> q;

// p 是数据类型为 int 型的队列,队头元素[p.top()]最小
priority_queue<int, vector<int>, greater<int> > p;

由上可知,如果要用到小顶堆,则要把模板的三个参数都带进去。
greater<> 是 STL里面定义的一个仿函数,对于基本类型可以用这个仿函数声明小顶堆。

对于自定义类型,则必须自己重载“ operator< ”(或者重载“>”)或者自己写仿函数。

struct node
{
    friend bool operator< (node n1, node n2)
    {
        return n1.priority < n2.priority;
        // 这里可根据需求改成: return n1.priority > n2.priority;
    }
    int priority;
    int value;
}; 

到这里应该对优先队列的用法有了大致的了解,那么我们现在回过头来看看最前面题目代码里面的一个疑惑:

struct Node {  
    int weight;
    friend bool operator <(Node a, Node b) {     
        return a.weight > b.weight;  
        //思考下这里为什么用'>'而不用'<'
    }  
};  

其实我开始是想这样写的:

struct Node {  
    int weight;
    friend bool operator <(Node a, Node b) {     
        return a.weight < b.weight;  
    }  
};  

// 然后这样使用:(但这种用法会报错)
priority_queue<Node, vector<Node>, greater<Node> > q; 

自定义类型重载“operator <”后,声明对象时就可以只带一个模板参数。不能这样声明:priority_queue<Node, vector<Node>, greater<Node> >,如果要这样使用,就要对“operator >”重载。针对前面的题目,代码可改成这样:

struct Node {  
    int weight;
    friend bool operator > (Node a, Node b) {     
        return a.weight > b.weight;  
    }  
};  
// 然后这样使用(这种用法是可以的):
priority_queue<Node, vector<Node>, greater<Node> > q; 

看过这些例子后,可得出如下结论:
对于自定义的类型,重载操作符“<”与只能和声明方法priority_queue<Node> q;联合使用,而重载操作符“>”则只能对应声明方法priority_queue<Node, vector<Node>, greater<Node> > q;

上面讲到的都只是优先队列的用法,下面是用堆来实现队列(注:这份代码是在网上找的,来源:http://blog.csdn.net/zhang20072844/article/details/10286997,重点烛知道如何对最大最小堆调整):

#include <iostream>  
#include <vector>  
using namespace std;  

class Heap {  
    public:  
        Heap(int iSize);  
        ~Heap();  
        int Enqueue(int iVal);  
        int Dequeue(int &iVal);  
        int GetMin(int &iVal);  
        void printQueue();  

    protected:  
        int *m_pData;  
        int m_iSize;  
        int m_iAmount;  
}; 

Heap::Heap(int iSize = 100) {  
    //注意这里是从0开始,所以如果根是i,那么左孩子是2*i+1,右孩子是2*i+2 
    m_pData = new int[iSize];  
    m_iSize = iSize;  
    m_iAmount = 0;  
} 

Heap::~Heap() {  
    delete []m_pData;  
}  

int Heap::Enqueue(int iVal)  {  //入堆 (入队列)
    if (m_iAmount == m_iSize) return 0; //队列已满 
    m_pData[m_iAmount ++] = iVal;  
    int iIndex = m_iAmount - 1;  
    while (m_pData[iIndex] < m_pData[(iIndex - 1) /2]) { //上浮,直到满足最小堆 
        swap(m_pData[iIndex],m_pData[(iIndex - 1) /2]);  
        iIndex = (iIndex - 1) /2;  
    }  
    return 1;  
}  

int Heap::Dequeue(int &iVal) { //出堆 (出队列)
    if (m_iAmount == 0)  return 0; 
    iVal = m_pData[0];//出堆的数据  
    m_pData[0] = m_pData[m_iAmount - 1];//最后一个数据放到第一个根上面  
    -- m_iAmount;//总数减1  
    int rc = m_pData[0];  
    int s = 0;  
    for (int j = 2*s +1; j < m_iAmount; j *= 2)//最后一个数放到第一个位置以后,开始下沉,来维持堆的性质  
    {  
        if (j < m_iAmount - 1 && m_pData[j] > m_pData[j+1]) ++ j;  
        if (rc < m_pData[j]) break; //rc应该插入在s位置上  
        m_pData[s] = m_pData[j];  
        s = j;  
    }  
    m_pData[s] = rc;  
    return 1;  
}  

int Heap::GetMin(int &iVal) {  
    if (m_iAmount == 0) return 0;
    iVal = m_pData[0];  
    return 1;  
}  

void Heap::printQueue() {  
    for (int i = 0; i < m_iAmount; ++ i) cout << m_pData[i] << " ";  
    cout << endl;  
}  

int main() {  
    Heap heap;  
    heap.Enqueue(4);  
    heap.Enqueue(1);  
    heap.Enqueue(3);  
    heap.Enqueue(2);  
    heap.Enqueue(6);  
    heap.Enqueue(5);  
    heap.printQueue();  

    int iVal;  
    heap.Dequeue(iVal);  
    heap.Dequeue(iVal);  

    heap.printQueue();  
    return 0;  
}  

OK,慢慢享用吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值