C++哈夫曼树+哈夫曼编码的实现(双完整版)

注释详解哈夫曼Tree和哈夫曼Code

  本文是根据B站视频👉青岛大学 - 王卓老师的数据结构来实现的,涉及到哈夫曼Tree 和 哈夫曼Code的C++版完整实现,若有不足欢迎大佬斧正-(/▽\)

一、哈夫曼Tree

  具体理论请配合👉B站视频来学习,构造哈夫曼Tree主要的方法如下:
  第一步:构造森林全是根
  第二步:选用两小造新树
  第三步:删除两小添新人(parent设置为 n+1 到 2n-1 中的下标)
  第四步:重复2、3步剩单根
  话不多说,我们只需要记住这四步,把下面代码的框架敲熟了,就能运用自如了。
  PS:代码有详细注解 和 引导思考,喜欢的话可以收藏一波~

#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;

typedef struct HFT
{
	int weight;
	int parent, LTree, RTree;
}HFT, *PHFT;

void Select(PHFT HFT, int n, int &s1, int &s2)    //选取权值最小的两个结点
{
    for(int i = 1; i < n; i++){         //初始化s1,s1的双亲为0
        if(HFT[i].parent == 0){     /**  思考:为什么不是 i <= n  **/
            s1 = i;					//解答:因为我们第 2n-1  个元素的parent是0
            break;                          /**  思考:为什么要加上 break  **/
        }
    }

    for(int i = 1; i < n; i++){         //s1为权值最小的下标
            if(HFT[i].parent == 0 && HFT[s1].weight > HFT[i].weight)
                s1 = i;
    }

    for(int j = 1; j < n; j++){         //初始化s2,s2的双亲为0
        if(HFT[j].parent == 0 && j != s1){
            s2 = j;
            break;
        }
    }

    for(int j = 1; j < n; j++){         //s2为另一个权值最小的下标
        if(HFT[j].parent == 0 && HFT[s2].weight > HFT[j].weight && j != s1)
            s2 = j;
    }
}

void initHFT(PHFT &H, int n)
{
	if(n <= 1) return;
	int m = 2*n - 1;            //数组共2n - 1个元素
	H = new HFT[m + 1];	//0号单元未用,H[m]表示根节点
    for(int i = 1; i <= m; i++){
        H[i].parent = 0;
        H[i].LTree = 0;
        H[i].RTree = 0;
    }
    cout << "please input the weight of nodes:" << endl;
	for(int i = 1; i <= n; i++)
        cin >> H[i].weight;
    cout << endl;
    for(int i = n + 1; i <= m; i++)         //产生的新结点要放在从n+1开始,一直到2n-1的位置
    {
        int s1, s2;
        Select(H, i, s1, s2);
        H[s1].parent = i;
        H[s2].parent = i;   //相当于从表F中删除s1, s2
        H[i].LTree = s1;
        H[i].RTree = s2;
        H[i].weight = H[s1].weight + H[s2].weight;
    }
}

void showHFT(PHFT &H, int n)
{
    cout << "index  weight  parent  LTree  RTree" << endl;
    cout << left;     //左对齐输出
    int m = 2*n - 1;
    for(int i = 1; i <= m; i++){
        cout << setw(5) << i << "  ";                       /** 思考: 为什么是setw(5) 和 setw(6)   **/
        cout << setw(6) << H[i].weight << "  ";     // 解答:当后面紧跟着的输出字段长度小于n的时候,在该字段前面用空格补齐;当输出字段长度大于n时,全部整体输出
        cout << setw(6) << H[i].parent << "  ";
        cout << setw(6) << H[i].LTree << "  ";
        cout << setw(6) << H[i].RTree << "  " << endl;
    }
}

int main()
{
    PHFT HFT;
    int n = 0;
    cout << "please input the number of nodes: ";
    cin >> n;
    initHFT(HFT, n);
    showHFT(HFT, n);
    system("pause");
	return 0;
}

  我们可以对应下面这张图来看代码,弄懂思路。
在这里插入图片描述
Input
please input the number of nodes: 7
please input the weight of nodes:
7
19
2
6
32
3
21

Output
index weight parent LTree RTree
1  7   0   0   0
2  19  11   0   0
3  2   8   0   0
4  6   9   0   0
5  32  12   0   0
6  3   8   0   0
7  21  12   0   0
8  5   9   3   6
9  11  10   8   4
10  18  11  1   9
11  37  13  10   2
12  53  13  7   5
13  90  0  11   12
  

二、哈夫曼Code

  该代码在实现哈夫曼编码核心算法时既使用了C++的string类来实现,也使用了C的方式实现。这是在学会构造哈夫曼树之后的进一步提升,在这里给需要提高的同学抛出一个思考问题,“C++如何处理模板类template实现自动根据用户输入的 weight 值类型来分配内存”。
在这里插入图片描述
  首先先看图,根据图来实现以下步骤(用char动态数组):
  第一步:构建哈夫曼树表、HC表(动态二维数组)、cd表(一维)
  第二步:一般规定左子树路径为0,右子树路径为1,按哈夫曼树表寻找parent结点直到为0。
  与第二步同时进行:先将临时cd表最后一个元素定为’\0’,创建临时结点记录当前处理的结点。
  第三步:将cd表值赋值给HC表,同时销毁cd表的临时内存。

在这里插入代码片

```cpp
#include <iostream>
#include <string.h>
#include <iomanip>
#include <stack>
#include <algorithm>

using namespace std;

typedef struct HFT
{
    float weight;
    int parent, LTree, RTree;
    string name;
}HFT, *PHFT;

void Select(const PHFT& H, const int& n, int& s1, int& s2){
    for(int i = 1; i < n; i++){
        if(H[i].parent == 0){
            s1 = i;
            break;
        }
    }
    for(int i = 1; i < n; i++){
        if(H[i].parent == 0 && H[s1].weight > H[i].weight)
            s1 = i;
    }

    for(int j = 1; j < n; j++){
        if(H[j].parent == 0 && j != s1){
            s2 = j;
            break;
        }
    }
    for(int j = 1; j < n; j++){
        if(H[j].parent == 0 && H[s2].weight > H[j].weight && j != s1)
            s2 = j;
    }
}

void initHFC(PHFT& HT, const int& n)
{
    if(n <= 1) return;
    int m = 2*n - 1;
    HT = new HFT[m + 1];
    for(int i = 1; i <= m; i++){
        HT[i].parent = 0;
        HT[i].LTree = 0;
        HT[i].RTree = 0;
    }
    cout << "please input the weight of nodes and nodes' name as 0.23 A: " << endl;
    for(int i = 1; i <= n; i++){
        cin >> HT[i].weight >> HT[i].name;
    }
    cout << endl;
    for(int i = n+1; i <= m; i++){
        int s1, s2;
        Select(HT, i, s1, s2);
        HT[s1].parent = i;
        HT[s2].parent = i;
        HT[i].LTree = s1;
        HT[i].RTree = s2;
        HT[i].weight = HT[s1].weight + HT[s2].weight;
    }
}

/**      char 类型解决方案   */
void ch_CreateHFMcode(const PHFT& HT, char** HC, const int& n)
{
    char* temp = new char[n];
    temp[n-1] = '\0';
    int  start = 0, c = 0, father = 0;
    for(int i = 1; i <= n; i++)
    {
        start = n - 1;
        c = i;                      //记录正在处理的当前位置
        father = HT[i].parent;
        while(father != 0)   //从叶子节点向上回溯
        {                //回溯一次 start指向前一个位置一个
            if(HT[father].LTree == c) temp[--start] = '0';
            else temp[--start] = '1';
            c = father;             //当前位置移到父节点
            father = HT[father].parent;    //更新父节点,继续向上回溯
        }
        HC[i] = new char[n-start];            // 为第 i 个字符串编码分配空间
        strcpy(HC[i], &temp[start]);             // 将求得的编码从临时空间cd复制到HC的当前行中,strcpy遇到 \0 拷贝就会结束
    }
    delete temp;
}

/**   string 类型的解决方案   **/
void str_CreateHFMcode(PHFT& H, string *HC, const int& n)
{
    string temp;
    stack<string> st;		//利用栈实现上一个char类型strcpy的方法
    int cur = 0, father = 0;
    for(int i = 1; i <= n; i++){
        cur = i;
        father = H[i].parent;
        while(father != 0)
        {
            if(H[father].LTree == cur) st.push("0");
            else st.push("1");
            cur = father;
            father = H[father].parent;
        }
        while(!st.empty()){
            temp += st.top();
            st.pop();
        }
        HC[i] = temp;
        temp.erase();	//擦除内存
    }
}

void showdata(const PHFT& HFT, char** HC, const int& n){
    cout << "index  weight  parent  LTree  RTree" << endl;
    cout << left;
    int m = 2*n - 1;
    for(int i = 1; i <= m; i++){
        cout << setw(5) << i << "  ";
        cout << setw(6) << HFT[i].weight << "  ";
        cout << setw(6) << HFT[i].parent << "  ";
        cout << setw(6) << HFT[i].LTree << "  ";
        cout << setw(6) << HFT[i].RTree << "  " << endl;
    }
    cout << endl;

    cout << "Name  HFMCode" << endl;
    for(int i = 1; i <= n; i++){
        cout << setw(5) << HFT[i].name << "  ";
        cout << setw(7) << HC[i] << "  " << endl;
    }
}

int main()
{
    PHFT HFT;
    int n = 0;
    cout << "please intput the number of vertices: ";
    cin >> n;
    char** HC = new char*[n];
    initHFC(HFT, n);
    ch_CreateHFMcode(HFT, HC, n);
    showdata(HFT, HC, n);
    return 0;
}

Input
please intput the number of vertice: 7
what value do you want to give them?
0.4
0.3
0.15
0.05
0.04
0.03
0.03

Output
index HC[i]
1  0
2  10
3  110
4  11111
5  11110
6  11100
7  11101
  
路曼曼其修远兮,吾将上下而求索

  • 59
    点赞
  • 273
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
### 回答1: 哈夫曼树是一种特殊的二叉树,用于实现哈夫曼编码哈夫曼编码是一种无损压缩数据的方式,可以将一个字符串或者文件转换为一个“01”序列,使其占用的空间更小。 构造哈夫曼树的过程首先需要计算每个字符出现的频率,然后将这些字符和频率建立起来一个数组。接着,以这个数组为基础构造哈夫曼树。具体的步骤是: 1. 将字符和频率存储在一个数组中。 2. 根据频率从小到大排序数组。 3. 取出数组中的前两个元素,将其权值相加生成一个新的节点,将这个节点放回数组中。 4. 重复第三步,直到只剩下一个节点。 5. 最后构造出来的即为哈夫曼树。 接着,就可以根据哈夫曼树实现哈夫曼编码了。哈夫曼编码规则是:在哈夫曼树中,从根节点到该字符所在叶子节点的路径中,若经过的左子树则输出0,经过的右子树则输出1。例如,对于字符串"hello",哈夫曼编码为: h: 110, e: 111, l: 01, o: 00。 总之,哈夫曼树哈夫曼编码是无损压缩算法中的经典算法,在各种压缩领域都有广泛应用。 ### 回答2: 哈夫曼树是一种树型数据结构,可以用来进行数据的压缩和解压缩。构造哈夫曼树算法通常采用贪心策略,即在每一步中选择权值最小的两个节点,并将它们合并成一个新的节点,直到形成一个根节点为止。 在构造哈夫曼树后,可以通过遍历树来获取每个字符的编码。具体来说,可以从根节点开始遍历,遇到左子树则在编码末尾添加0,遇到右子树则在编码末尾添加1,直到达到叶子节点,即可得到该字符的哈夫曼编码哈夫曼编码的主要优势在于其具有变长编码,即不同字符的编码长度可以不同。这比固定长度编码更加高效,因为在压缩稀疏数据时,短编码可以显著减少编码长度。 下面我们来实现哈夫曼编码的具体代码,以字符串"hello world"为例: 1.首先需要统计每个字符出现的次数,并按照出现次数从小到大排序,以便构造哈夫曼树。 ``` from collections import Counter string = "hello world" counter = dict(Counter(string)) sorted_count = sorted(counter.items(), key=lambda x: x[1]) ``` 2.使用sorted_count中的数据构造哈夫曼树。 ``` class Node(object): def __init__(self, value, freq): self.value = value self.freq = freq self.left = None self.right = None def build_huffman_tree(sorted_count): nodes = [Node(value=c[0], freq=c[1]) for c in sorted_count] while len(nodes) > 1: node1 = nodes.pop(0) node2 = nodes.pop(0) new_node = Node(value="", freq=node1.freq + node2.freq) new_node.left = node1 new_node.right = node2 nodes.append(new_node) nodes = sorted(nodes, key=lambda x: x.freq) root = nodes[0] return root root = build_huffman_tree(sorted_count) ``` 3.遍历哈夫曼树,获取每个字符的编码。 ``` def traverse_tree(node, code, codes): if node is None: return if node.value: codes[node.value] = code return traverse_tree(node.left, code + "0", codes) traverse_tree(node.right, code + "1", codes) codes = {} traverse_tree(root, "", codes) print(codes) ``` 输出结果为: ``` {'h': '1110', 'w': '11000', 'r': '11001', 'd': '11010', 'e': '001', ' ': '010', 'l': '000', 'o': '1111'} ``` 这就是"hello world"字符串中每个字符的哈夫曼编码。使用这些编码可以非常高效地将原始数据进行压缩。 ### 回答3: 哈夫曼树是一种经典的树形结构,可以用来实现哈夫曼编码哈夫曼编码是一种基于变长编码的数据压缩算法,它利用出现频率较高的字符来用较短的编码来表示,从而实现对数据的压缩。 哈夫曼树的构造方法是:首先将所有的字符按照出现频率从小到大排序,然后将出现频率最小的两个字符合并成一棵二叉树,其权值为两个字符的权值之和。然后将合并后的二叉树插入到原来的序列中,重新排序。然后再将出现频率最小的两个字符合并成一棵二叉树,以此类推,直到构造出一棵包含所有字符的二叉树,这就是哈夫曼树哈夫曼编码实现方法是:对于哈夫曼树上的每一个叶子节点,将其路径上的左右分支分别标记为0和1,得到该叶子节点对应的编码。然后将每个字符对应的编码存储起来,即可得到该字符串的哈夫曼编码哈夫曼编码具有很好的压缩效果,因为它可以使得出现频率较高的字符用较短的编码表示,从而减小了编码的总长度,实现了对数据的压缩。同时,哈夫曼编码还具有唯一解的性质,即每个字符都有唯一的编码,从而避免解压缩时出现歧义。因此,哈夫曼编码在数据压缩领域得到了广泛的应用。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ac君

在你们的鼓励下我会多多分享代码

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值