C++ Huffman树实现文件的压缩与解压

前言

Huffman树在数据结构的时候都了解过,由Huffman树可以生成huffman编码,而Huffman编码在解决文件压缩问题的时候还是一个比较经典的算法。

Huffman树 ?

定义:Huffman树,又称最优二叉树,是加权路径长度最短的二叉树
在这里插入图片描述

生成Huffman树

假设有这样一组权值 1,3,5,7,Huffman树构建过程如下:
在这里插入图片描述

生成Huffman编码

Huffman树中左子树路径标为0,右子树路径标为1。从根节点到叶子节点经过的路径的组合即为该叶子节点的编码
在这里插入图片描述

Huffman编码压缩的原理

1. 统计待压缩文件中每个字符出现的次数

  • 构建一个结构体,存放信息:字符,次数,编码
  • 遍历源文件,将每个字符的次数写入对应的结构体信息中

2. 将次数作为权值构建Huffman树

  • 采用优先级队列,但优先级队列是大的元素在前面,所以要自行更改一下
  • 普通的二叉树只能通过父结点找子节点,但在获取编码的时候需要通过叶子节点往根节点走,所以构建二叉树时,要有孩子结点指向父结点

3. 通过Huffman数获取每个字符所对应的编码

  • 通过叶子节点找根节点是编码是逆序的,所以要reverse

4. 向压缩文件中写入信息

  • 压缩文件中不仅仅要存放编码信息
  • 第一行:存放原文件的后缀,因为在解压缩文件的时候会用到
  • 第二行:存放原文件字符的个数n(叶子节点)
  • 接下来的n行:字符,次数(为了解压缩重建Huffman树)
  • 接下来才是压缩编码

例如:原文件名为“1.txt”,存放ABBBCCCCCDDDDDDD“”
则:压缩文件中存放:

解压缩

1. 获取后缀
2. 获取字符以及对应的次数
3. 重建Huffman树
4. 解压压缩数据

a. 从压缩文件中读取一个字节ch
b. 从根节点开始,按照ch的8个比特位信息从高到低遍历huffman树:

  • 该比特位是0,取当前节点的左孩子,否则取右孩子
  • 直到遍历到叶子节点位置,该字符就被解析成功,将解压出的字符写入文件
  • 如果在遍历huffman过程中,8个比特位已经比较完毕还没有到达叶子节点,从a开始执行

c. 重复以上过程,直到所有的数据解析完毕。

遇到的问题

1. 只压缩字母时成功,压缩文字会崩溃?

那是因为刚开始创建存放信息的数据是char类型的,当文件中有文字时,一个文字占两个字节,并且每个字节的范围是128~255,如果是char类型,数组的下标就会变成负数,数字越界了, 需要将存放信息的数据类型变为unsigned char

2. 为什么存放文字时依然可以找到,不会冲突?

一个字节有八个bit位,那么范围也就是0~255,如果文字的第一个字节为65,岂不是和‘A’冲突了。实际上不存在,因为文字的每个字节的范围是128~256, 所以不会发生冲突。

3. 解压大文件时只能解压一部分??
  1. 首先检查压缩后的文件是否正常(可以使用UE)

  2. 将压缩文件采用二进制显示, 发现存在好几个FF

  3. 一般情况下,文件指针碰到EOF就表示到文件结尾了,因为EOF是 -1,也就是FF,所以只解压了一部分(解压到第一个FF就停止了)

  4. 采用 feof()函数,多加一个判断即可,feof()函数就是判断文件末尾,而不仅仅是碰见EOF停止

4. 关于位运算操作

因为在压缩和解压的过程中都用到的位操作,需注意:位操作针对的是每一个bit位,一个字节有8个bit位,在循环到从0~7的时候一定要记得清0

5. 为什么压缩照片的时候会失败?

我自己刚开始在压缩大文件的时候(包含文字)没有任何的问题,但是一旦压缩、解压照片就会出问题。经过几番调试,终于发现是因为 ‘\0’ 的问题,因为如果刚开始把(字节,次数)先写入 buf 中,再由 buf 通过 fwrite 函数写入文件中,一定会出现问题(可能出现 0 字节)。因为 fwrite 的第一个参数要求C格式的字符串,把 buf 转化为C格式的字符串,如果遇见 ‘\0’,就会停止,所以就会崩溃。不要将字节写入 buf 中,而是通过 fputc 直接把字节写入文件中,然后再把 (, 次数)写入 buf 中,再将buf写入文件即可。

注意:buf 一定要定义在循环内,因为每次都要重新往 buf 中写入内容,或者手动把 buf 清空

for (size_t i = 0; i < 256; i++)
	{
		if (info[i]._chCount != 0)
		{
			string buf;  //buf中存放  ,次数
			fputc(info[i]._ch, fOut);//必须先把ch放进去,如果把ch作为string的字符最后转换为C的字符,会导致'\0'没有处理
			buf = ',';
			_itoa(info[i]._chCount, countStr, 10);
			buf += countStr;
			fputs(buf.c_str(), fOut);
			fputc('\n', fOut);
		}
	}

源码

源码

  • 11
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是C++中基于哈夫曼进行文件压缩解压的示例代码: ```c++ #include <iostream> #include <fstream> #include <unordered_map> #include <queue> #include <bitset> using namespace std; struct HuffmanNode { char c; int freq; HuffmanNode *left, *right; HuffmanNode(char c, int freq) : c(c), freq(freq), left(nullptr), right(nullptr) {} ~HuffmanNode() { delete left; delete right; } }; struct compare { bool operator()(const HuffmanNode* a, const HuffmanNode* b) { return a->freq > b->freq; } }; class HuffmanTree { private: HuffmanNode* root; unordered_map<char, int> freq_map; void build_freq_map(const string& str) { for (char c : str) { freq_map[c]++; } } void build_tree() { priority_queue<HuffmanNode*, vector<HuffmanNode*>, compare> pq; for (auto& p : freq_map) { pq.push(new HuffmanNode(p.first, p.second)); } while (pq.size() > 1) { auto left = pq.top(); pq.pop(); auto right = pq.top(); pq.pop(); auto parent = new HuffmanNode('\0', left->freq + right->freq); parent->left = left; parent->right = right; pq.push(parent); } root = pq.top(); } void build_encoding_table(unordered_map<char, string>& encoding_table, HuffmanNode* node, string code) { if (!node) return; if (node->c != '\0') { encoding_table[node->c] = code; } build_encoding_table(encoding_table, node->left, code + "0"); build_encoding_table(encoding_table, node->right, code + "1"); } public: HuffmanTree(const string& str) : root(nullptr) { build_freq_map(str); build_tree(); } ~HuffmanTree() { delete root; } unordered_map<char, string> build_encoding_table() { unordered_map<char, string> encoding_table; build_encoding_table(encoding_table, root, ""); return encoding_table; } void compress(const string& input_file, const string& output_file) { ifstream ifs(input_file, ios::binary); if (!ifs) { cerr << "Failed to open input file!" << endl; return; } ofstream ofs(output_file, ios::binary); if (!ofs) { cerr << "Failed to open output file!" << endl; return; } // 构建编码表 auto encoding_table = build_encoding_table(); // 计算压缩后的文件头 int header_size = sizeof(int) + freq_map.size() * (sizeof(char) + sizeof(int)); char* header = new char[header_size]; int pos = 0; memcpy(&header[pos], &freq_map.size(), sizeof(int)); pos += sizeof(int); for (auto& p : freq_map) { memcpy(&header[pos], &p.first, sizeof(char)); pos += sizeof(char); memcpy(&header[pos], &p.second, sizeof(int)); pos += sizeof(int); } // 写入文件头 ofs.write(header, header_size); // 压缩文件内容 char c; string code; while (ifs.get(c)) { code += encoding_table[c]; while (code.size() >= 8) { char byte = bitset<8>(code.substr(0, 8)).to_ulong(); ofs.write(&byte, sizeof(char)); code = code.substr(8); } } if (!code.empty()) { char byte = bitset<8>(code + string(8 - code.size(), '0')).to_ulong(); ofs.write(&byte, sizeof(char)); } ifs.close(); ofs.close(); } void decompress(const string& input_file, const string& output_file) { ifstream ifs(input_file, ios::binary); if (!ifs) { cerr << "Failed to open input file!" << endl; return; } ofstream ofs(output_file, ios::binary); if (!ofs) { cerr << "Failed to open output file!" << endl; return; } // 读取文件头 int header_size; ifs.read(reinterpret_cast<char*>(&header_size), sizeof(int)); char* header = new char[header_size]; ifs.read(header, header_size); int pos = 0; int freq_map_size; memcpy(&freq_map_size, &header[pos], sizeof(int)); pos += sizeof(int); unordered_map<char, int> freq_map; for (int i = 0; i < freq_map_size; ++i) { char c; int freq; memcpy(&c, &header[pos], sizeof(char)); pos += sizeof(char); memcpy(&freq, &header[pos], sizeof(int)); pos += sizeof(int); freq_map[c] = freq; } delete[] header; // 构建哈夫曼 priority_queue<HuffmanNode*, vector<HuffmanNode*>, compare> pq; for (auto& p : freq_map) { pq.push(new HuffmanNode(p.first, p.second)); } while (pq.size() > 1) { auto left = pq.top(); pq.pop(); auto right = pq.top(); pq.pop(); auto parent = new HuffmanNode('\0', left->freq + right->freq); parent->left = left; parent->right = right; pq.push(parent); } root = pq.top(); // 解压文件内容 HuffmanNode* node = root; int bit_count = 0; char byte; while (ifs.get(byte)) { bitset<8> bits(byte); for (int i = 7; i >= 0; --i) { if (bits[i] == 0) { node = node->left; } else { node = node->right; } if (node->c != '\0') { ofs.write(&node->c, sizeof(char)); node = root; } if (++bit_count == 8 * header_size + node->freq) break; } if (bit_count == 8 * header_size + node->freq) break; } ifs.close(); ofs.close(); } }; int main() { string input_file = "input.txt"; string compressed_file = "compressed.bin"; string decompressed_file = "decompressed.txt"; // 压缩文件 HuffmanTree huffman_tree("abracadabra"); huffman_tree.compress(input_file, compressed_file); // 解压文件 huffman_tree.decompress(compressed_file, decompressed_file); return 0; } ``` 在上面的示例中,我们首先定义了一个`HuffmanNode`结构体,用于表示哈夫曼的节点。每个节点包含一个字符`c`和该字符在原始本中出现的频率`freq`,以及指向左右子节点的指针`left`和`right`。 接着,我们定义了一个`HuffmanTree`类,用于构建哈夫曼,并提供压缩解压文件的功能。在构建哈夫曼时,我们使用了一个优先队列`pq`,将每个字符的频率作为优先级进行排序。每次从队列中取出两个频率最小的节点,合并成一个新的节点,然后将新节点加入队列。当队列中只剩下一个节点时,即为哈夫曼的根节点。 在压缩文件时,我们首先构建了编码表`encoding_table`,将每个字符映射到对应的二进制编码。然后,我们计算压缩后的文件头,并将其写入输出文件中。文件头包含了字符频率的数量以及每个字符和频率的信息。接着,我们读取输入文件中的每个字符,并将其转换为对应的二进制编码,然后将编码写入输出文件中。 在解压文件时,我们首先读取文件头,并使用其中的字符频率信息构建哈夫曼。然后,我们读取压缩后的文件内容,并按位进行解码。每次读取一个二进制位时,我们通过遍历哈夫曼找到对应的字符,并将其写入输出文件中。当我们写入的字符数量等于原始本中的字符数量时,解压过程结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值