课程设计选择了文本文件压缩,因为我觉得我对文件操作是一点不会,所以选择了最陌生的领域
我的基本思路:
我的文本文件压缩是由哈夫曼编码实现的,由于要构建哈夫曼树我手搓了一个小顶堆的数据结构,因为课程设计要求不能使用stl库所以我就写了个类来实现优先队列,由于想用map但是又不想手搓二叉平衡搜索树所以直接定义了长度为256的二维字符数组储存字符对应的哈夫曼编码,再遍历文件再对字节处理就能将哈夫曼编码一位一位写入压缩的dat文件中,通过反序列化哈夫曼树到对应的压缩文件那里,到时候解压的时候先反序列化哈夫曼树再由遍历哈夫曼编码确定左子树还是右子树,遇到字符节点就将字符写入文件中,再将指针重新指向哈夫曼树的头节点,循环下去就能得到原文件。目前主要问题就是如果末尾不足八位该如何处理
class MinHeap {
private:
vector<HufTree*> heap;
// 返回父节点索引
int parent(int i) { return (i - 1) / 2; }
// 返回左子节点索引
int left(int i) { return 2 * i + 1; }
// 返回右子节点索引
int right(int i) { return 2 * i + 2; }
// 交换两个元素
void swap(int a, int b) {
HufTree* temp = heap[a];
heap[a] = heap[b];
heap[b] = temp;
}
// 自底向上调整堆
void heapifyUp(int i) {
while (i > 0 && *heap[parent(i)] > *heap[i]) {
swap(i, parent(i));
i = parent(i);
}
}
// 自顶向下调整堆
void heapifyDown(int i) {
int smallest = i;
int l = left(i);
int r = right(i);
if (l < heap.size() && *heap[l] < *heap[smallest]) {
smallest = l;
}
if (r < heap.size() && *heap[r] < *heap[smallest]) {
smallest = r;
}
if (smallest != i) {
swap(i, smallest);
heapifyDown(smallest);
}
}
public:
// 插入元素
void insert(HufTree* a) {
heap.push_back(a);
int index = heap.size() - 1;
heapifyUp(index);
}
// 弹出堆顶元素
HufTree* extractMax() {
//if (heap.empty()) {
// throw runtime_error("Heap is empty");
//}
HufTree* root = heap[0];
heap[0] = heap.back();
heap.pop_back();
heapifyDown(0);
return root;
}
// 获取堆顶元素
HufTree* getMax() {
if (heap.empty()) {
throw runtime_error("Heap is empty");
}
return heap[0];
}
// 判断堆是否为空
bool isEmpty() {
return heap.empty();
}
};
下面是实现构造哈夫曼编码的代码
HufTree* setHufTree(string s,long long an[]) {
std::ifstream file(s);
// 检查文件是否成功打开
if (!file.is_open()) {
std::cerr << "无法打开文件: " << s << std::endl;
return nullptr;
}
// 逐个字符读取文件内容
char ch;
while (file.get(ch)) {
an[ch]++;
}
// 关闭文件
file.close();
MinHeap pqueue;
num = 0;
for (int i = 0; i < 256; ++i) {
if (an[i]) {
num++;
HufTree* c = new HufTree();
c->frequency = an[i];
c->val = static_cast<char>(i);
c->number = num;
c->left = nullptr; c->right = nullptr; c->yes = true;
pqueue.insert(c);
}
}
while (!pqueue.isEmpty()) {
HufTree* a = pqueue.getMax();
pqueue.extractMax();
if (pqueue.isEmpty()) return a;
HufTree* b = pqueue.getMax();
pqueue.extractMax();
HufTree* c = nodesum(a, b);
c->number = ++num;
pqueue.insert(c);
}
}
下面是转码的过程
string CodeFilecompressfile_address = thiscompressfile_address + "/CodeFile.dat";
ofstream dataFile(CodeFilecompressfile_address, ios::binary);
ifstream OriFile(thisfile_address, ios::binary);
if (dataFile.is_open()&& OriFile.is_open()) {
char zhi;
uint8_t currentByte = 0; // 当前字节,用于累积位
int bitCount = 0;// 当前字节中已有的位数
while (OriFile.get(zhi)) {
for(char cccc: temporarily[zhi])
if (cccc == '0') {
currentByte = (currentByte << 1) | 0; // 将0加入当前字节的最低位
bitCount++; // 写入二进制位0
}
else if (cccc == '1') {
currentByte = (currentByte << 1) | 1; // 将1加入当前字节的最低位
bitCount++;
}
else {
// 可以添加适当的错误处理,处理不合法的输入字符
cerr << "遇到非法字符 '" << zhi << "',无法转换为二进制位." << endl;
}
}
dataFile.close();
cout << "压缩数据已保存到 " << thisfile_address << endl;
}
else {
cerr << "无法打开文件以保存压缩数据: " << thisfile_address << endl;
}