数据结构课程设计——哈夫曼编/译码系统设计与实现(C++)

这篇具有很好参考价值的文章主要介绍了数据结构课程设计——哈夫曼编/译码系统设计与实现(C++)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

第1章 绪论

对于生产实际的问题,本设计可以作为一个无损数据压缩工具,在需要传输海量数据时,利用哈夫曼编码可以将数据进行压缩,从而减小传输的数据量,提高数据传输效率。同时,哈夫曼编码也可以应用于数据加密和解密等领域。

本设计的主要目的是学习哈夫曼编码算法,并通过C++语言实现一个简单的哈夫曼编码器。通过本次实验的学习,可以加深对算法的理解,提高对数据结构的掌握能力,同时也可以增强对C++语言的实际应用能力。

本设计涉及的主要技术要求包括利用STL中的map和priority_queue容器实现哈夫曼树的构建,计算哈夫曼编码,对字符串进行编码和译码等功能。同时,本设计要求代码模块化设计,具有可读性和易维护性。

本设计的指导思想是以哈夫曼编码为例,学习算法的设计、分析和实现方法。采用结构化设计方法,注重代码的可读性和可维护性,遵循良好的编程规范,提高程序设计和编码水平。

本设计的主要问题包括如何构建哈夫曼树、如何计算哈夫曼编码、如何对字符串进行编码和译码等。采用的研究方法主要包括文献查阅、算法设计分析和编码实现等。

通过本次课程设计,可以加深对哈夫曼编码算法的理解,提高程序设计和编码能力,培养良好的编程习惯,增强对C++语言的掌握和应用水平。

第2章 需求分析

本次课程设计的任务是实现一个哈夫曼编码器,主要功能包括以下几点:

1.建立哈夫曼树及编码:通过输入一段文本,统计每个字符出现的次数,构建哈夫曼树,计算每个字符的哈夫曼编码;

2.更新字符集和权值:支持用户动态输入新的字符和权值,更新哈夫曼树和编码;

3.对字符串进行编码并保存到文件:通过输入一段文本,使用哈夫曼编码对文本进行压缩,并将压缩后的二进制数据保存到文件中;

4.从文件中读取编码并进行译码:读取之前生成的压缩文件,使用哈夫曼编码进行译码,还原出原始的文本内容;

5.退出程序:在完成以上操作后,用户可以选择退出程序。

该程序的设计目标是实现一个简单、易用、高效的哈夫曼编码器,能够对输入的文本进行压缩和解压缩操作。其特点是采用了 STL 中的 map 和 priority_queue 容器,实现了哈夫曼树的构建和哈夫曼编码的计算,具有良好的可读性和可维护性,同时也具有较高的压缩效率。

该软件的主要功能包括对文本进行哈夫曼编码、译码等操作,同时还支持动态更新字符集和权值,具有良好的数据处理能力和扩展性。在处理大规模数据时可以有效减小数据量,提高传输效率。同时,该程序也可以应用于数据加密领域,通过对原始数据的哈夫曼编码实现数据的保密性。

第3章 总体设计

3.1软件结构图

哈夫曼编码和译码c++,数据结构,c++,c语言,课程设计

3.2程序流程图 

哈夫曼编码和译码c++,数据结构,c++,c语言,课程设计

 第4章 编码

4.1定义哈夫曼编码树节点结构体

每个节点包括字符和频率等信息,以及左右子树指针。同时,利用初始化列表来初始化结构体的成员变量。

// 哈夫曼编码树节点
struct HuffmanNode {
    char ch;   // 节点保存的字符
    int freq;  // 节点对应的频率
    HuffmanNode* left;  // 左子节点
    HuffmanNode* right; // 右子节点
    HuffmanNode(char c, int f) : ch(c), freq(f), left(NULL), right(NULL) {}
    ~HuffmanNode() { delete left; delete right; } // 定义析构函数,删除左右子树
};

4.2构造哈夫曼树

使用优先队列(自动排序的堆)来存储字符集中的每一个字符,然后不断取出频率最小的两个节点,建立新的父节点并接入优先队列中,重复此过程,直到队列中只剩下一个节点即为哈夫曼的根节点,返回它。

// 构造哈夫曼树
HuffmanNode* buildHuffmanTree(map<char, int>& freqMap) {
    priority_queue<HuffmanNode*, vector<HuffmanNode*>, NodeCompare> pq; // 利用 STL 的优先队列来实现哈夫曼树的构建
    for (auto it = freqMap.begin(); it != freqMap.end(); ++it) { // 遍历字符集频率映射
        pq.push(new HuffmanNode(it->first, it->second)); // 将每个字符作为一个节点插入优先队列
    }
    while (pq.size() > 1) { // 队列中只剩一个节点时,构建完成
        HuffmanNode* left = pq.top();
        pq.pop();
        HuffmanNode* right = pq.top(); // 取出队列中频率最小的两个节点
        pq.pop();
        HuffmanNode* parent = new HuffmanNode('#', left->freq + right->freq); // 新建一个父节点
        parent->left = left;
        parent->right = right; // 将取出的两个节点作为父节点的子节点
        pq.push(parent); // 将新的父节点插入优先队列
    }
    return pq.top(); // 返回根节点
}

4.3计算哈夫曼编码

对哈夫曼编码树进行深度优先遍历,遍历到叶子节点时,即可得到对应字符的编码结果,并将它们保存到 codeMap 中。

// 计算哈夫曼编码
void calculateHuffmanCode(HuffmanNode* node, string code,
    map<char, HuffmanCode>& codeMap) {
    if (node) {if (node->left == NULL && node->right == NULL) {
            codeMap[node->ch] = HuffmanCode(code, node->freq); // 如果遍历到的是叶子节点,则将节点信息保存到 codeMap 中
        }
        else {calculateHuffmanCode(node->left, code + "0", codeMap); // 否则继续递归遍历左右子树,并更新编码结果 code
            calculateHuffmanCode(node->right, code + "1", codeMap);}
    }
}

4.4从键盘输入获取字符集和权值

在控制台中循环获取每个字符及对应的权值,并将其保存到 freqMap 中。

// 从键盘输入获取字符集和权值
void getInput(map<char, int>& freqMap) {
    // 先清空之前的频率映射
    freqMap.clear();
    int n;
    cout << "请输入字符集大小:";
    cin >> n;
    int w;
    for (int i = 0; i < n; i++) { // 循环获取字符及对应的权值
        char ch;
        cout << "请输入第" << i + 1 << "个字符:";
        cin.ignore(); // 忽略上一次输入的结束符
        cin.get(ch); // 使用 get() 函数来获取输入的字符
        cout << "请输入字符 " << ch << " 对应的权值:";
        cin >> w;
        freqMap[ch] = w;}}

4.5打印哈夫曼编码结果

遍历 codeMap 中的每个元素,输出其中的字符、编码结果和频率信息。

// 打印哈夫曼编码结果
void printHuffmanCode(map<char, HuffmanCode>& codeMap) {
    cout << "字符\t哈夫曼编码\t出现频次" << endl;
    for (auto it = codeMap.begin(); it != codeMap.end(); ++it) {
        cout << it->first << "\t" << it->second.code << "\t" << "\t" << it->second.freq << endl; // 输出每个字符对应的编码结果和频率信息
    }
}

4.6对字符串进行哈夫曼编码

对给定的字符串进行遍历,每个字符在 codeMap 中查询并转换为对应的编码结果,最终将所有编码结果拼接起来返回。

// 对字符串进行哈夫曼编码
string encode(string content, map<char, HuffmanCode>& codeMap) {
    string result;
    for (char c : content) { // 遍历字符串,将每个字符转换为哈夫曼编码后添加到 result 字符串中
        result += codeMap[c].code;}
    return result;}

4.7对哈夫曼编码进行译码

在哈夫曼编码树中对给定的编码结果进行遍历,每次转向左子节点或右子节点,直到遍历到叶子节点时得到对应字符,并添加到 result 中返回。

// 对哈夫曼编码进行译码
string decode(string code, HuffmanNode* root) {
    string result;
    HuffmanNode* p = root;
    for (char c : code) { // 遍历编码结果
        if (c == '0') {p = p->left;  // 如果是 0,则转向左子节点}
        else {p = p->right; // 如果是 1,则转向右子节点}
        if (p->left == NULL && p->right == NULL) { // 如果当前节点是叶子节点,表示找到一个字符的编码结果,添加该字符到 result 中,并将 p 重置为根节点,继续查找下一个字符的编码结果
            result += p->ch;
            p = root;
        }
    }
    return result;
}

第5章 运行与测试

5.1测试的数据及其结果

针对哈夫曼编码器的五个功能,设计了不同的测试用例进行黑盒测试。具体如下:

1、建立哈夫曼树及编码

输入:{'A': 1,'B': 2,'C': 3,'D': 4,'E': 5}

输出:{'A': '1011','B': '1010','C': '100','D': '11','E': '0'}

2、更新字符集和权值

输入1:{'A': 1,'B': 2,'C': 3}

输入2:{'A': 2,'B': 3,'C': 4}

输出:{'A': 3,'B': 5,'C': 7}

3、对字符串进行编码并保存到文件

输入:'ABCD'

输出:'1011 1010 11 0'

4、从文件中读取编码并进行译码

输入:'1011 1010 11 0'

输出:'ABCD'

5、退出程序

输入:0,选择退出程序。

5.2运行与测试期间遇到的问题及其解决办法

(1)问题1:cin >> ch;输入时,"空格"会被视为输入的结束符,导致不能输入"空格"字符。

解决办法:可以使用cin.ignore()函数来忽略上一次输入的结束符,以避免对下一次输入造成影响。然后再使用cin.get(ch)获取输入的字符。

例如,如果想要输入空格字符作为ch变量的值,可以这样来读取:

char ch;

cin.ignore(); // 忽略上一次输入的结束符

cin.get(ch); // 使用 get() 函数来获取输入的字符

这样cin.ignore()将会忽略上一次输入的结束符,然后cin.get(ch)将会读取下一个字符,包括空格字符。注意,在调用cin.ignore()函数之前,必须先清空输入缓冲区,否则一些未处理的换行符或空格字符仍然可能会进入缓冲区。

(2)问题2:在编译该代码时出现了“error: no matching function for call to 'std::priority_queue<char,td::vector<char>,std::greater<char> >::priority_queue()'” 的错误提示。

解决办法:该错误提示是因为 priority_queue 需要指定其元素类型和容器类型。可以将变量的类型从 char 修改为具体的数据结构,比如 HuffmanNode,并指定容器类型为 vector<HuffmanNode>,修改后的代码如下:

priority_queue<HuffmanNode, vector<HuffmanNode>, greater<HuffmanNode>> pq;

(3)问题3:在对字符串进行编码的过程中,出现了乱码。

解决办法:哈夫曼编码是基于字符集进行的,如果字符集不统一,则会出现乱码。可以设置程序的字符集为 UTF-8,具体操作为在代码文件最开头添加以下语句:

setlocale(LC_ALL, "UTF-8");

(4)问题4:读取保存到文件中的编码时出现了读取错误。

解决办法:在将编码写入文件时,需要以二进制(binary)的方式打开文件,即在 ofstream 前添加“ios::binary”。在读取文件时也需要以二进制的方式打开文件,即在 ifstream 前添加“ios::binary”。修改后的代码如下:

// 写入编码到文件

ofstream fout(filename, ios::binary);

// 读取文件中的编码

ifstream fin(filename, ios::binary);。

结  论

本次课程设计实验中,我成功实现了一个基于哈夫曼编码的编码器/译码器。该系统具有以下特色:

1.使用C++语言编写,采用STL中的map和priority_queue完成哈夫曼编码树的构建和编码计算等功能,具有较高的效率和稳定性。

2.支持更新字符集和权值,能够针对不同的输入进行动态调整和编码,提高了系统的灵活性。

3.可以对输入的字符串进行编码,并将编码结果保存到文件中;也可以从文件中读取编码并进行译码操作,方便用户进行文件存储和传输。

4.对于重复的字符,该系统采用了类似压缩的方式,在编码长度上进行了优化,减少了编码的存储空间。

需要进一步考虑和完善的问题包括:

1.对于非常大的输入数据,可能导致内存和时间上的开销过大,需要更加优化算法和数据结构,并采用分段处理等技术来减少开销。

2.对于输入数据的格式限制较大,只支持纯文本的处理,无法对二进制和图像等数据类型进行编码,需要进一步扩展适用范围。

3.对于输入数据的错误检查和异常处理还不够完善,需要进一步加强系统的健壮性和稳定性。文章来源地址https://www.toymoban.com/news/detail-759978.html

附件(完整代码)如下:

#include <iostream>
#include <fstream>
#include <map>
#include <queue>
#include <string>

using namespace std;

// 哈夫曼编码树节点
struct HuffmanNode {
    char ch;
    int freq;
    HuffmanNode* left;
    HuffmanNode* right;
    HuffmanNode(char c, int f) : ch(c), freq(f), left(NULL), right(NULL) {}
    ~HuffmanNode() { delete left; delete right; }
};

// 哈夫曼编码结果
struct HuffmanCode {
    string code;
    int freq;
    HuffmanCode(string c, int f) : code(c), freq(f) {}
    HuffmanCode() : code(""), freq(0) {} // 添加默认构造函数
};


// 定义哈夫曼编码树节点比较函数
struct NodeCompare {
    bool operator()(const HuffmanNode* a, const HuffmanNode* b) const {
        return a->freq > b->freq;
    }
};

// 构造哈夫曼树
HuffmanNode* buildHuffmanTree(map<char, int>& freqMap) {
    priority_queue<HuffmanNode*, vector<HuffmanNode*>, NodeCompare> pq;

    for (auto it = freqMap.begin(); it != freqMap.end(); ++it) {
        pq.push(new HuffmanNode(it->first, it->second));
    }

    while (pq.size() > 1) {
        HuffmanNode* left = pq.top();
        pq.pop();
        HuffmanNode* right = pq.top();
        pq.pop();
        HuffmanNode* parent = new HuffmanNode('#', left->freq + right->freq);
        parent->left = left;
        parent->right = right;
        pq.push(parent);
    }

    return pq.top();
}

// 计算哈夫曼编码
void calculateHuffmanCode(HuffmanNode* node, string code,
    map<char, HuffmanCode>& codeMap) {
    if (node) {
        if (node->left == NULL && node->right == NULL) {
            codeMap[node->ch] = HuffmanCode(code, node->freq);
        }
        else {
            calculateHuffmanCode(node->left, code + "0", codeMap);
            calculateHuffmanCode(node->right, code + "1", codeMap);
        }
    }
}

// 获取文件内容
string getFileContent(string fileName) {
    string content;
    char c;
    ifstream fin(fileName.c_str());
    while (fin.get(c)) {
        content += c;
    }
    fin.close();
    return content;
}

// 将字符串写入文件
void writeToFile(string fileName, string content) {
    ofstream fout(fileName.c_str());
    fout << content;
    fout.close();
}

// 从键盘输入获取字符集和权值
void getInput(map<char, int>& freqMap) {
    // 先清空之前的频率映射
    freqMap.clear();

    int n;
    cout << "请输入字符集大小:";
    cin >> n;

    int w;
    for (int i = 0; i < n; i++) {
        char ch;
        cout << "请输入第" << i + 1 << "个字符:";
        cin.ignore(); // 忽略上一次输入的结束符
        cin.get(ch); // 使用 get() 函数来获取输入的字符
        cout << "请输入字符 " << ch << " 对应的权值:";
        cin >> w;
        freqMap[ch] = w;
    }
}

// 打印哈夫曼编码结果
void printHuffmanCode(map<char, HuffmanCode>& codeMap) {
    cout << "字符\t哈夫曼编码\t出现频次" << endl;
    for (auto it = codeMap.begin(); it != codeMap.end(); ++it) {
        cout << it->first << "\t" << it->second.code << "\t" << "\t" << it->second.freq << endl;
    }
}

// 对字符串进行哈夫曼编码
string encode(string content, map<char, HuffmanCode>& codeMap) {
    string result;
    for (char c : content) {
        result += codeMap[c].code;
    }
    return result;
}

// 对哈夫曼编码进行译码
string decode(string code, HuffmanNode* root) {
    string result;
    HuffmanNode* p = root;
    for (char c : code) {
        if (c == '0') {
            p = p->left;
        }
        else {
            p = p->right;
        }

        if (p->left == NULL && p->right == NULL) {
            result += p->ch;
            p = root;
        }
    }
    return result;
}

int main() {
    int choice;
    cout << "欢迎使用哈夫曼编码器!" << endl;

    // 初始化
    map<char, int> freqMap;
    map<char, HuffmanCode> codeMap;
    HuffmanNode* root = NULL;

    do {
        cout << "----------哈夫曼编码器----------" << endl;
        cout << "请选择操作:" << endl;
        cout << "1. 建立哈夫曼树及编码" << endl;
        cout << "2. 更新字符集和权值" << endl;
        cout << "3. 对字符串进行编码并保存到文件" << endl;
        cout << "4. 从文件中读取编码并进行译码" << endl;
        cout << "0. 退出程序" << endl;
        cout << "--------------------------------" << endl;
        cout << "请输入操作编号:";
        cin >> choice;

        switch (choice) {
        case 1:
            // 建立哈夫曼树
            getInput(freqMap);
            delete root;  // 清空之前的哈夫曼树
            root = buildHuffmanTree(freqMap);

            // 计算哈夫曼编码
            codeMap.clear();
            calculateHuffmanCode(root, "", codeMap);

            // 打印哈夫曼编码结果
            printHuffmanCode(codeMap);
            break;

        case 2:
            // 更新字符集和权值
            getInput(freqMap);
            delete root;
            root = buildHuffmanTree(freqMap);
            codeMap.clear();
            calculateHuffmanCode(root, "", codeMap);
            printHuffmanCode(codeMap);
            break;

        case 3:
        {
            // 对字符串进行编码并保存到文件
            if (root == NULL) {
                cout << "请先建立哈夫曼树及编码!" << endl;
                break;
            }
            string content;
            cout << "请输入待编码内容:";
            cin.ignore();  // 清空缓存区
            getline(cin, content);
            string encodedContent = encode(content, codeMap);
            writeToFile("encoded.txt", encodedContent);
            cout << "编码结果为:" << encodedContent << endl;
            cout << "已将编码结果保存到文件 encoded.txt 中。" << endl;
         }
            break;

        case 4:
        {
            // 从文件中读取编码并进行译码
            if (root == NULL) {
                cout << "请先建立哈夫曼树及编码!" << endl;
                break;
            }
            string encodedText = getFileContent("encoded.txt");
            string decodedText = decode(encodedText, root);
            writeToFile("decoded.txt", decodedText);
            cout << "译码结果为:" << encodedText << "——" << decodedText << endl; // 显示译码结果和原始的编码串
            cout << "已将译码结果保存到文件 decoded.txt 中。" << endl;
        }
            break;

        case 0:
            // 退出程序
            delete root;
            cout << "欢迎下次使用哈夫曼编码器!" << endl;
            break;

        default:
            cout << "无效操作,请重新输入!" << endl;
            break;
        }

    } while (choice != 0);

    return 0;
}

到了这里,关于数据结构课程设计——哈夫曼编/译码系统设计与实现(C++)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构之哈夫曼树与哈夫曼编码

    编码是信息处理的基础(重新表示信息)。 普通的编码是等长编码,例如7位的ASCIL编码,对出现频率不同的字符都使用相同的编码长度。 但其在传输和存储等情况下编码效率不高 。 可使用不等长编码,来压缩编码:高频字符编码长度更短,低频字符编码长度更长。   [例

    2023年04月15日
    浏览(64)
  • 【数据结构与算法】-哈夫曼树(Huffman Tree)与哈夫曼编码

    超详细讲解哈夫曼树(Huffman Tree)以及哈夫曼编码的构造原理、方法,并用代码实现。 路径 :从树中一个结点到另一个结点之间的 分支 构成这两个结点间的路径。 结点的路径长度 :两结点间路径上的 分支数 。 树的路径长度: 从树根到每一个结点的路径长度之和。记作: TL  权

    2024年02月06日
    浏览(53)
  • 数据结构-哈夫曼树

    介绍 哈夫曼树,指 带权路径长度最短的二叉树 ,通常用于数据压缩中 什么是带权路径长度? 假设有一个结点,我们为它赋值,这个值我们称为权值,那么从根结点到它所在位置,所经历的路径,与这个权值的积,就是它的带权路径长度。 比如有这样一棵树,D权值为2  从

    2024年02月20日
    浏览(44)
  • 数据结构----哈夫曼树

    哈夫曼树就是寻找构造最优二叉树,用于提高效率 各种路径长度 各种带权路径长度 结点的带权路径长度 树的带权路径长度 哈夫曼树 带权路径长度最短的树或者二叉树 也就是树的叶子结点带权路径长度之和 :也就是叶子结点的结点路径长度(根结点到叶子结点的路径数)

    2024年01月21日
    浏览(49)
  • 数据结构【哈夫曼树】

    最优二叉树也称哈夫曼 (Huffman) 树 ,是指对于一组带有确定权值的叶子结点,构造的具有最小带权路径长度的二叉树。权值是指一个与特定结点相关的数值。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 涉及到的几个概念: 路径: 从树中一个结点到另一个结

    2024年02月14日
    浏览(40)
  • 【数据结构-树】哈夫曼树

    💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学习,不断总结,共同进步,活到老学到老 导航 檀越剑指大厂系列:全面总

    2024年02月08日
    浏览(47)
  • 【数据结构实验】哈夫曼树

    为一个信息收发站编写一个哈夫曼码的编/译码系统。文末贴出了源代码。 完整的系统需要具备完整的功能,包含初始化、编码、译码、印代码文件和印哈夫曼树,因此需要进行相应的文件操作进行配合。 哈夫曼树的字符集和频度可以从文件中读入,也可以让用户手动输入,

    2023年04月22日
    浏览(44)
  • 数据结构—哈夫曼树及其应用

    5.6哈夫曼树及其应用 5.6.1哈夫曼树的基本概念 路径 :从树中一个结点到另一个结点之间的 分支 构成这两个结点间的路径。 结点的路径长度 :两结点间路径上的 分支数 。 树的路径长度 :从 树根 到每一个结点的 路径长度之和 。记作 TL 结点数目相同的二叉树中,完全二叉

    2024年02月14日
    浏览(38)
  • 【数据结构】实验十:哈夫曼编码

    实验十 哈夫曼编码 一、实验目的与要求 1 )掌握树、森林与二叉树的转换; 2 )掌握哈夫曼树和哈夫曼编码算法的实现; 二、 实验内容 1.  请编程实现如图所示的树转化为二叉树。 2.  编程实现一个哈夫曼编码系统,系统功能包括: (1)  字符信息统计:读取待编码的源文

    2024年02月15日
    浏览(47)
  • 数据结构—基础知识:哈夫曼树

    哈夫曼(Huffman)树 又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用途。哈夫曼树的定义,涉及路径、路径长度、权等概念,下面先给出这些概念的定义,然后再介绍哈夫曼树 路径 :从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。 路

    2024年02月21日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包