哈夫曼编码问题再续(下篇)——优先队列求解

上篇描述了哈夫曼编码问题的基本描述以及建造一个哈夫曼树的过程分析,那么当算法已经描述清楚之后,我们要怎么样来实现

代码呢?或者说,给你一些带有权值的叶子节点,要怎么样利用程序快速算出所对应的哈夫曼树的带权路径WPL呢?

我们首先回顾一下上篇讲到的那个问题:

例如有这一个字符串“good
good study day day up”,现在我们要对字符串进行哈夫曼编码,该字符串一共有 26 个字符,10 种字符,我们首先统计出每个字符的频率,然后按从大到小顺序排列如下(第二列的字符是空格):

最后,我们根据每个字符出现的频率,建造出了这样的一棵哈夫曼树:

然后根结点到每个叶子结点的路径便是其对应字母的编码了,于是我们可以得到:

然后就是计算一下哈夫曼树的带权路径长度
WPL,也就是每个叶子节点的权值乘以到根的距离(即每个叶子节点的深度,在这个例子中根节点为第0层)结果之和。

WPL
= 5 * 2 + 5 * 3 + 4 * 3 + 3 * 3 + 2 * 4 + 2 * 4 + 2 * 4 + 1 * 4 + 1 * 4 + 1 * 4 = 82。

算法描述得非常清楚了,那么我们思考这样一个问题,要怎么将具体的算法实现成相应代码呢?是否模拟上述过程,每次都是取其中权值最小的两个节点呢?这样的话在数据量较小的情况下是可以实现的,但是如果当数据量比较大的时候,比如说节点数到达10^6以上时,那么每取两个节点,加入新的节点后就要重新排序一次,然后在整棵哈夫曼树建立完成之后,再根据每个叶节点的深度以及权值,计算整棵哈夫曼树的带权路径WPL,但是这样的话会由于时间复杂度过大而无法在短时间内运行出程序结果。

那么问题来了,我们不用这种方法去计算WPL,还有其他的办法吗?

首先我们来看这棵构造好的哈夫曼树:

为了简便起见,我们从树的左边开始考虑,即B,E,F节点。

对于节点B,其深度为3,权值为5,那么其带权路径长度为5*3
= 15;

那么我们再看一下节点B的父亲节点,其权值为9,是由权值为4和权值为5的节点B构造而成,那么即是9 = 4 + 5;

同样的再往上一层,节点B的爷爷节点,其权值为16,是由权值为9和权值为7的节点构造而成,而权值为9的节点的构造前面已经说明,则有16 = 4 + 5 + 7;

再往上一层就到根节点了。

那么到这里我们可以看到,节点B的父亲节点和爷爷节点的组成部分都有节点B的“功劳”,即节点B的权值是其另外两个的“组成部分”,那么节点B的带权路径长度即为其到根节点路径上(不包含根节点),与其(或者说是与其父节点,爷爷节点等)有父子关系的节点抽取出节点B的组成部分(包括节点B本身),再全部相加,这样的话就得到了节点B的带权路径长度为5
+ 5 + 5 = 15;

同样的,节点E,F按照同样的方法进行推导。

所以我们从上面的分析得出:

每个带权叶节点到根节点的带权路径长度等于其到根节点路径上所有节点的包含该带权叶节点权值组成部分之和。

因此,最后我们推导出,所有叶节点,即整棵哈夫曼树的带权路径长度
WPL即为:

除了根节点以外,所有节点的权值之和。

如上图哈夫曼树的带权路径长度
WPL即为:

WPL
= 16 + 10 + 9 + 7 + 5 + 5 + 4 + 5 + 3 + 4 + 2 + 3 + 2 + 2 + 2 + 1 + 1 + 1 = 82

有了这样的判断之后,我们便很容易计算出一颗哈夫曼树的带权路径WPL了。

因此我们可以借助一个叫做优先队列的数据结构,而优先队列的实现往往是借助于二叉堆的结构实现,在这里我们要实现的是小根堆的数据结构。一开始的时候,我们可以将所有的节点一个一个的压入队列中,每次有节点入队,队列都会进行自调整,使其保持一个小根堆的状态。当所有的节点全部入队之后,这时候我们根据以上推导出来的结论,每次取两个权值最小的节点,将其值计算之后,然后再将两个节点权值之和的节点压入队列中,直到队列中只剩下一个节点(即根节点),跳出循环体,输出最后的答案。即整棵哈夫曼树的带权路径WPL。

完整代码实现(C++版,非STL):

#include<iostream>
using namespace std;
class Heap {
private:
    int *data, size;
public:
    Heap(int length_input) {
        data = new int[length_input];
        size = 0;
    }
    ~Heap() {
        delete[] data;
    }
    void push(int value) {
        data[size] = value;
        int current = size;
        int father = (current - 1) / 2;
        while (data[current] < data[father]) {
            swap(data[current], data[father]);
            current = father;
            father = (current - 1) / 2;
        }
        size++;
    }
    int top() {
         return data[0];
    }
    void update(int pos, int n) {
        int lchild = 2 * pos + 1, rchild = 2 * pos + 2;
        int max_value = pos;
        if (lchild < n && data[lchild] < data[max_value]) {
            max_value = lchild;
        }
        if (rchild < n && data[rchild] < data[max_value]) {
            max_value = rchild;
        }
        if (max_value != pos) {
            swap(data[pos], data[max_value]);
            update(max_value, n);
        }
    }
    void pop() {
        swap(data[0], data[size - 1]);
        size--;
        update(0, size);
    }
    int heap_size() {
        return size;
    }
};
int main() {
    int n,value,ans = 0;
    cin >> n;
    Heap heap(n);     //表示队列中的元素的上限
    for(int i = 1;i <= n;++i){
        cin >> value;
        heap.push(value);
    }
    if(n==1){
        ans = ans + heap.top();
    }
    while(heap.heap_size() > 1){
        int a = heap.top();
        heap.pop();
        int b = heap.top();
        heap.pop();
        ans += a + b;
        heap.push(a+b);
    }
    cout << ans << endl;
    return 0;
}

完整代码实现(C++版,STL优先队列实现):

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int main(){
    int num[10];
    priority_queue <int,vector <int>,greater <int> > que;
    printf("叶节点的权值分别为:\n");
    for(int i = 0;i < 10;++i){
        scanf("%d",&num[i]);
        que.push(num[i]);
    }
    int ans = 0;
    while(que.size() > 1){
        int a = que.top();
        que.pop();
        int b = que.top();
        que.pop();
        ans += a + b;
        que.push(a + b);
    }
    printf("所对应的哈夫曼树的带权路径长度WPL = %d\n",ans);
    return 0;
}

如有错误,还请指正,O(∩_∩)O谢谢

时间: 2024-10-24 08:07:21

哈夫曼编码问题再续(下篇)——优先队列求解的相关文章

机智零崎不会没梗Ⅱ (哈夫曼编码、优先队列)

题目描述 你满心欢喜的召唤出了外星生物,以为可以变身超人拥有强大力量战胜一切怪兽,然而面对着身前高大的外星生物你一脸茫然,因为,你懂M78星云语吗?不过不用担心,因为零崎非常机智,他给出了关键性的提示:“讲道理,日语可是全宇宙通用语,所以为什么不试试和外星人讲日语呢?” 不过现在外星生物说的话都是“[email protected]#$%^&%#%I&!……”这样的东西,你要怎么转换成日语呢? 作位全宇宙通用的日语,自然有一套万能的转换算法,那就是Huffman编码转换!当然了这肯定不是普

hdoj 1053 Entropy(用哈夫曼编码)优先队列

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1053 讲解: 题意:给定一个字符串,根据哈夫曼编码求出最短长度,并求出比值. 思路:就是哈夫曼编码.把单个字符出现次数作为权值. AC代码: 1 #include <iostream> 2 #include <string> 3 #include <queue> 4 #include <cstdio> 5 using namespace std; 6 7 cla

HDU 1053 Entropy(哈夫曼编码 贪心+优先队列)

传送门: http://acm.hdu.edu.cn/showproblem.php?pid=1053 Entropy Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 7233    Accepted Submission(s): 3047 Problem Description An entropy encoder is a data

哈夫曼编码(Huffman coding)的那些事,(编码技术介绍和程序实现)

前言 哈夫曼编码(Huffman coding)是一种可变长的前缀码.哈夫曼编码使用的算法是David A. Huffman还是在MIT的学生时提出的,并且在1952年发表了名为<A Method for the Construction of Minimum-Redundancy Codes>的文章.编码这种编码的过程叫做哈夫曼编码,它是一种普遍的熵编码技术,包括用于无损数据压缩领域.由于哈夫曼编码的运用广泛,本文将简要介绍: 哈夫曼编码的编码(不包含解码)原理 代码(java)实现过程 一

哈夫曼编码

   1.问题描述 哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法.其压缩率通常在20%-90%之间.哈夫曼编码算法用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式.一个包含100,000个字符的文件,各字符出现频率不同,如下表所示. 有多种方式表示文件中的信息,若用0,1码表示字符的方法,即每个字符用唯一的一个0,1串表示.若采用定长编码表示,则需要3位表示一个字符,整个文件编码需要300,000位:若采用变长编码表示,给频率高的字符较短的编码:频率低的字符较长的

Huffman Coding 哈夫曼编码

作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4096079.html 使用优先队列实现,需要注意以下几点: 1.在使用priority_queue时,内部需要存储哈夫曼树节点的指针,而不能是节点.因为构建哈夫曼树时,需要把其左右指针指向孩子,而如果储存的是节点,那么孩子的地址是会改变的.同理节点应当使用new在内存中开辟,而不能使用vector,原因是vector在数组大小为2整数次幂时,大小会倍增,开辟新数组并把老数组的数字copy过

贪心算法-霍夫曼编码

霍夫曼编码是一种无损数据压缩算法.在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度.期望值降低,从而达到无损压缩数据的目的.例如,在英文中,e的出现机率最高,而z的出现概率则最低.当利用霍夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26).用普通的表示方法时,每个

哈夫曼(Huffman)树与哈夫曼编码

声明:原创作品,转载时请注明文章来自SAP师太技术博客:www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4289610.html 哈夫曼树又称最优二叉树,是一种带权路径长最短的树.树的路径长度是从树根到每一个叶子之间的路径长度之和.节点的带树路径长度为从该节点到树根之间的路径长度与该节点权(比如字符在某串中的使用频率)的乘积. 比如有一串字符串如

【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算

1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R 中找出关键字与K 相同的记录).一个自然的问题就是,如何实现有效率的查找? 静态查找:集合中记录是固定的,没有插入和删除操作,只有查找 动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除 静态查找--方法一:顺序查找(时间复杂度O(n)) int SequentialSearch(St