实验5 二叉树的应用程序设计

实验预备知识:

1.掌握二叉树的创建和遍历算法。

2.掌握哈夫曼编码原理。

一、实验目的

1.进一步掌握二叉树的存储结构和相应算法。

2.掌握哈夫曼树树的创建和哈夫曼编码。

二、实验环境

⒈ 硬件:每个学生需配备计算机一台。操作系统:Windows

⒉ 软件:Windows操作系统+Visual C++。 

三、实验要求

1.要求采用二叉链表作为存储结构,完成哈夫曼树的创建。

2.输出对应数据的哈夫曼编码,并求出平均编码长度。

四、实验内容

1.在自己的U盘的“学号+姓名”文件夹中创建“实验5”文件夹,本次实验的所有程序和数据都要求存储到本文件夹中。

2.现在某电报公司假设有10字符进行编码,这10个字符的使用频率如下表所示,请创建哈夫曼树。

A

B

C

D

E

F

G

H

I

J

19

18

16

14

12

8

6

4

2

1

  1. 编写函数求出A~J的哈夫曼编码。
#include <iostream>
#include <queue>
#include <map>
using namespace std;

struct Node {
    char data; // 字符
    int freq; // 频率
    Node* left;
    Node* right;

    Node(char d, int f) : data(d), freq(f), left(nullptr), right(nullptr) {}


//    这里的Node(char d, int f) : data(d), freq(f), left(nullptr), right(nullptr) {}
// 是一个构造函数,用于创建Node对象并进行初始化。
//        它采用了成员初始化列表的方式,将传入的参数d和f分别赋值给data和freq成员变量,
// 并将left和right指针初始化为nullptr(空指针)。
//使用成员初始化列表的方式可以提高代码的效率和可读性。
//        在构造函数体中,如果需要对成员变量进行赋值,
// 需要使用赋值运算符,这会导致多余的内存分配和拷贝操作。
//        而使用成员初始化列表可以直接对成员变量进行初始化,
// 避免了这些额外的操作,从而提高了代码的效率。

    //就相当于java的构造函数
        
};

struct Compare {
    bool operator()(Node* a, Node* b) {
        return a->freq > b->freq;   
    }
};
//权值越大,在树的前面越小,就是最优的路径解


// 创建哈夫曼树
Node* createHuffmanTree(map<char, int>& freqMap) {
    priority_queue<Node*, vector<Node*>, Compare> pq;//排序
    //这个容器相当于是队列的容器,是其中里面的,
    //是队列中存放的空间为容器。


    //插入数据的步骤,导入数据和排序
    for (const auto& pair : freqMap) {
        Node* node = new Node(pair.first, pair.second);


        //    for (const auto& pair : freqMap) { ... } 是 C++11 引入的一种新的循环语法,
        // 叫做基于范围的 for 循环(range - based for loop)。
        //
        //        在这个循环中,freqMap 是你要遍历的容器,c
        // onst auto& pair 是每次迭代时从 freqMap 中取出的元素。
        //
        //        解释一下 const auto& pair:
        //
        //        auto 是 C++11 引入的一种新的类型推断机制。
        // 编译器会自动推断 pair 的类型,你不需要显式地指定。
        //        在这个例子中,freqMap 是一个 map<char, int> 类型的容器,
        // 所以 pair 的类型会被推断为 pair<const char, int>。
        //
        //        const 表示这个变量是常量,你不能修改它的值。
        // 这是一种好的编程习惯,因为它可以防止你在循环中意外地修改了 pair 的值。
        //
        //        & 表示引用。如果没有& ,那么每次迭代时,
        // 都会从 freqMap 中复制一个元素到 pair。
        //如果 pair 的类型很大,那么这将会是一个很耗时的操作。
        // 但是有了& ,pair 就是 freqMap 中元素的一个引用,不会发生复制,可以提高代码的效率。


   /*     new Node(pair.first, pair.second) 这部分代码调用了 Node 类的构造函数,
            创建一个新的 Node 对象。这个构造函数接受两个参数:pair.first 和 pair.second。
            在这个上下文中,pair 是 freqMap 中的一个元素,它是一个键值对。
            pair.first 是字符(char 类型),pair.second 是该字符的频率(int 类型)。*/

        pq.push(node);//插入
    }

    //开始构建哈夫曼树
    while (pq.size() > 1) {
        Node* left = pq.top();
        pq.pop();
        Node* right = pq.top();
        pq.pop();

        Node* parent = new Node('$', left->freq + right->freq); // 虚拟节点
        //这里的$只是随便找了个字符充当字符的位置,没什么特殊意义
        //核心是为了合成根节点

        parent->left = left;
        parent->right = right;
        //这两步就是为了连接后面的数据的
        //返回的只是根节点,但是后面节点的遍历和数据都存在left和right节点中

        pq.push(parent);
    }

    return pq.top();//最后size为1时,一定是根节点

}

// 遍历哈夫曼树获取编码
void getHuffmanCodes(Node* root, string code, map<char, string>& huffmanCodes) {
    if (root == nullptr) {
        return;
    }

    if (root->left == nullptr && root->right == nullptr) { // 叶子节点
        huffmanCodes[root->data] = code;
        //获取编码关键步骤
    }

    getHuffmanCodes(root->left, code + "0", huffmanCodes);
    getHuffmanCodes(root->right, code + "1", huffmanCodes);
}

// 输出哈夫曼编码
void printHuffmanCodes(map<char, string>& huffmanCodes) {
    for (const auto& pair : huffmanCodes) {
        cout << pair.first << ": " << pair.second << endl;
        //      char                   string
    }
}

// 计算平均编码长度
float getAverageCodeLength(map<char, int>& freqMap, map<char, string>& huffmanCodes) {
    float totalFreq = 0;
    float totalCodeLen = 0;

    for (const auto& pair : freqMap) {
        totalFreq += pair.second;
        totalCodeLen += pair.second * huffmanCodes[pair.first].length();
        //频率*单个字符的编码长度
        //huffmanCodes[pair.first]其实就是huffmanCodes的pair.second
        //只不过我们现在在按照freqMap循环.
        //而他们的pair.first是相同的
        //其实本质上来说,
        //freqMap用于存放频率
        //huffmanCodes用于存放编码
    }

    return totalCodeLen / totalFreq;
}

int main() {
    map<char, int> freqMap = {
        {'A', 19},
        {'B', 18},
        {'C', 16},
        {'D', 14},
        {'E', 12},
        {'F', 8},
        {'G', 6},
        {'H', 4},
        {'I', 2},
        {'J', 1}
    };//原始数据

    Node* root = createHuffmanTree(freqMap);
    //哈夫曼树的根节点,里面包括整个哈夫曼树

    map<char, string> huffmanCodes;//这个是用来获取哈夫曼树编码的哈希表
    getHuffmanCodes(root, "", huffmanCodes);

    cout << "Huffman Codes:" << endl;
    printHuffmanCodes(huffmanCodes);

    float averageCodeLength = getAverageCodeLength(freqMap, huffmanCodes);
    cout << "Average Code Length: " << averageCodeLength << endl;

    return 0;
}