首页 > 其他分享 >哈夫曼树

哈夫曼树

时间:2023-02-17 13:33:51浏览次数:31  
标签:Node 编码 结点 哈夫曼 路径 minHeap

1.定义

1.1 哈夫曼树

哈夫曼树是一种最基本的压缩编码方法。对于如图所示的两棵二叉树,每个叶子节点都带有权值:

从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。例如,树 a 中根节点到结点 D 的路径长度为 4,树 b 中根节点到结点 D 的路径长度为 2。树的路径长度就是从树根到每一结点的路径长度之和。因此,树 a 的路径长度就为 20,树 b 的路径长度为 16。

如果考虑到带权的结点,结点的带权的路径长度为从该结点到树根之间的路径长度与结点上权的乘积。树的带权路径长度为树中所有叶子结点的带权路径长度之和

例如,树 a 的 \(WPL=5*1+15*2+40*3+30*4+10*4=315\)。而树 b 的 \(WPL=5*3+15*3+40*2+30*2+10*2=220\)。

其中,【带权路径长度(WPL)】最小的二叉树称作【哈夫曼树】,也称作最优二叉树

将树 a 转化为哈夫曼树的步骤如下:

  1. 首先把带有权值的叶子结点按照从小到大的顺序排列成一个有序序列,即:A5,E10,B15,D30,C40。
  2. 然后取头两个最小权值的结点作为一个新结点 N1 的两个孩子结点,注意相对较小的是左孩子,这里 A 是 N1 的左孩子,E 为 N1 的右孩子。则新节点的权值为两个叶子结点的权值的和,即 5+10=15。
  3. 然后将 N1 替换 A 与 E,插入有序序列中,保持从小到大的顺序排列。即 N115,B15,D30,C40。
  4. 重复步骤 2。将 N1 与 B 作为一个新的结点 N2 的两个子结点。N2 的权值为 15+15=30。
  5. 重复上述步骤,直到排列完成。

此时哈夫曼树的 \(WPL=40*1+30*2+15*3+10*4+5*4=205\)。

1.2 哈夫曼编码

哈夫曼树最早应用于电文编码。一般地,设需要编码的字符集为 \(d_1,d_2,……,d_n\),各个字符在电文中出现的次数或者频率集合为 \(w_1,w_2,……,w_n\),以 \(d_1,d_2,……,d_n\) 作为叶子结点,以 \(w_1,w_2,……,w_n\) 作为相应叶子结点的权值来构造一棵哈夫曼树。规定哈夫曼树的左分支代表 0,右分支代表 1,则从根结点到叶子结点所经过的路径分支组成的 0 和 1 的序列为该结点对应字符的编码,这就是哈夫曼编码

其编码过程如下图所示:

哈夫曼编码不同于 ASCII 和 Unicode 这些字符编码,这些字符集中的码长都采用的是长度相同的编码方案,而哈夫曼编码使用的是变长编码,而且哈夫曼编码满足立刻可解码性(就是说任一字符的编码都不会是另一个更长字符编码的前缀),这样当一个字符的编码中的位被接收时,可以立即进行解码而无须等待之后的位来决定是否存在另一个合法的更长的编码。例如,下面两个表中,表 1 不满足立刻可解码性,而表 2 则满足:

因为哈夫曼编码中每一个字符都位于哈夫曼树的叶子节点中,所以不会存在路径包含问题,这也就是立刻可解码性的原因。

为什么有时候采用哈夫曼编码压缩小文件时文件大小会变大?原因是在采用哈夫曼编码对文件内容进行逐字节编码后,不仅需要按位存储编码后的内容,还需要把原始文件中的字节数据以及权值信息存储到压缩文件当中。有了这些信息才可以在解码时正确构建哈夫曼树,然后进行解码过程。

2.代码实现

2.1 节点定义

哈夫曼树的节点定义如下:

struct Node {
	Node(char data, uint weight)
		: data_(data)
		, weight_(weight)
		, left_(nullptr)
		, right_(nullptr) { }
		
	char  data_;
	uint  weight_; // 节点的权值
	Node* left_;
	Node* right_;
};

2.2 构建哈夫曼树

在构建哈夫曼树的过程中,我们不断取出序列中权值最小的两个节点,因此,我们可以使用小根堆来进行构建,其定义如下:

class HuffmanTree {
public:
    HuffmanTree()
    : root_(nullptr)
    , minHeap_([](Node* n1, Node* n2) -> bool {
        return n1->weight_ > n2->weight_;
    }) {
	}
	
private:
    Node* root_;
    // 存储字符对应的哈夫曼编码
    unordered_map<char, string> codeMap_;
    
    using MinHeap = priority_queue<Node*, vector<Node*>, function<bool(Node*, Node*)>>;
    MinHeap minHeap_;
};

那么,构建哈夫曼树的代码如下:

void HuffmanTree::create(const string& str) {
    // 先统计字符的权值
    unordered_map<char, uint> dataMap;
    for (char ch : str) {
        dataMap[ch]++;
    }
    
    // 生成节点 放入小根堆中
    for (auto& pair : dataMap) {
        minHeap_.push(new Node(pair.first, pair.second));
    }
    
    while (minHeap_.size() > 1) {
        // 取出两个权值最小的节点
        Node* n1 = minHeap_.top();
        minHeap_.pop();
        Node* n2 = minHeap_.top();
        minHeap_.pop();
        
        // 生成父节点
        Node* node   = new Node('\0', n1->weight_ + n2->weight_);
        node->left_  = n1;
        node->right_ = n2;
        
        minHeap_.push(node);
    }
    
    root_ = minHeap_.top();
    minHeap_.pop();
}

2.3 构建哈夫曼编码

构建哈夫曼编码的过程可以采用前序遍历的方式进行实现,其代码如下:

void HuffmanTree::getHuffmanCode(Node* node, string code) {
    if (node->left_ == nullptr && node->right_ == nullptr) {
        codeMap_[node->data_] = code;
        return;
    }
    
    getHuffmanCode(node->left_, code + "0");
    getHuffmanCode(node->right_, code + "1");
}

2.4 编码和解码

编码的代码如下:

string HuffmanTree::encode(const string& str) {
    string res;
    for (char ch : str) {
        res.append(codeMap_[ch]);
    }
    return res;
}

解码的代码如下:

string HuffmanTree::decode(const string& str) {
    string res;
    Node*  cur = root_;
    for (char ch : str) {
        if (ch == '0') {
            cur = cur->left_;
        } else {
            cur = cur->right_;
        }
        
        if (cur->left_ == nullptr && cur->right_ == nullptr) {
            res.push_back(cur->data_);
            cur = root_;
        }
    }
    return res;
}

标签:Node,编码,结点,哈夫曼,路径,minHeap
From: https://www.cnblogs.com/tuilk/p/17129835.html

相关文章

  • 6.5用二叉树实现哈夫曼编码
       莫尔斯编码是根据日常文本中各字符出现频率决定表示各字符的编码的数据长度。不过,该编码体系,对AAAAAABBCDDEEEEEEF这样的特殊文并不是最合适的。在莫尔斯编码中,E......
  • 6.4通过莫尔斯编码来看哈夫曼算法的基础
       哈夫曼算法是哈夫曼(D.A.Huffman)于1952年提出来的压缩算法。日本人比较常用的压缩软件LHA,使用的就是哈夫曼算法。   文本文件是由不同类型的字符组合而成的......
  • 6.5 用二叉树实现哈夫曼编码
    莫尔斯编码是根据日常文本中各字符的出现频率来决定表示各字符的编码的数据长度的。不过,该编码体系,对AAAAAABBCDDEEEEEF这样的特殊文本并不是最适合的。在莫尔斯编码中,E的......
  • 哈夫曼树
    感觉是一个平凡的结论,但是我想了一年怎么证!!!结论:序列\(a\)生成的哈夫曼树,第\(i\)个元素的深度为\(\mathcal{O}(\log\suma_i-\loga_i+1)\)。考虑哈夫曼树的生成过......
  • 6.6哈夫曼算法能够大幅提升压缩比率
    使用哈夫曼树后,出现频率越高的数据所占用的数据位数就越少而且数据的区分也可以很清晰地实现。通过图6-5的步骤2可以发现,在用枝条连接数据时,我们是从出现频率较低的数......
  • 6.6哈夫曼算法能够大幅提升压缩比率
    使用哈夫曼树后,出现频率越高的数据所占用的数据位数就越少,而且数据的区分也可以很清晰地实现。但哈夫曼算法为什么达到这么好的效果呢,大家都了解吗?通过图6-5的步骤2可以发......
  • 6.4通过莫尔斯编码来看哈夫曼算法的基础
    压缩技巧实际上有很多种。接下来,我们就来看一下本章要介绍的第二个压缩技巧,即哈夫曼算法。哈夫曼算法是哈夫曼(D.A.Huffman)于1952年提出来的压缩算法。日本人比较常用的......
  • 6.4通过莫尔斯编码来看哈夫曼算法的基础
    哈夫曼算法是1952年提出的压缩算法,日本人常用的压缩软件LHA使用的就是哈夫曼算法。文本文件是由不同类型的字符组合而成的,而且不同的字符出现的次数也是不同的。  注......
  • [数据结构] 哈夫曼树
    哈夫曼树哈夫曼树简介给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(HuffmanTree)。哈夫曼......
  • 哈夫曼树复习
    哈夫曼编码--最基本的压缩编码方法哈夫曼树,特殊的二叉树 哈夫曼树的定义与原理:WPL最小构造步骤1,先把有权值的叶子结点按照,从小到大的顺序排列成一个有序序列2,取头两......