C语言哈夫曼树压缩/解压器

C语言哈夫曼树压缩/解压器

小编是大一的菜鸡,这个题目是数据结构的一个实验题,为了完成这个作业,查找了各种资料,借鉴了很多人的代码,前后折腾了三天左右。代码可能跟网上的不一样,大佬路过请不要踩我。

温馨提醒

建议先认真学习Huffman树、文件操作、位或与等相关知识。代码小编已经加了很多标注,如果有不清楚的地方欢迎交流~

原理

建立Huffman树,可以获得权值总和最小的二叉树,对叶子结点用0或1进行二进制编码,由于一个字节的二进制含有8位,将字符转换为二进制进行存储,可以大大减小存储空间。

头文件

#include<stdio.h>
#include<stdlib.h>
#include<string>//需用到strcpy()
#define MAX_SIZE 100//文件名长度
#define ERROR -1
#define OK 1``

哈夫曼树存储结构

typedef struct {
    unsigned int weight;//字符权重
    unsigned int parent, lchild, rchild;
}HTNode,*HuffmanTree;//动态分配数组存储哈夫曼树
typedef char** HuffmanCode;//动态分配数组存储哈夫曼编码表

哈夫曼树相关函数

这些函数基本上都是在严蔚敏的《数据结构》上抄过来的

void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, unsigned int* w, int n) {
    //这里的w为无符号类型
    Create_HuffmanTree(HT, w, n);
    Create_HT_Codelist(HT, HC, n);
}
void Create_HuffmanTree(HuffmanTree& HT, unsigned int* w, int n) {
    if (n <= 1) 
        return;
    int m = 2 * n - 1;//结点数
    HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//多出一个未用的0号单元
    if (!HT) 
        exit(ERROR);
    HTNode* p=HT+1;//跳过0号单元
    int i = 1;
    for (; i <= n; i++, p++)
        *p = { w[i-1],0,0,0 };//n个结点初始化,课本是用*w,最后w++,这里是为防止w[0]地址的丢失
    for (; i <= m; ++i, ++p) 
        *p = { 0,0,0,0 };//大于n的结点初始化
    for (i = n + 1; i <= m; ++i) {
        //建立哈夫曼树
        //在HT[i..i-1]选择parent为0且weight最小的两个结点,其序号分别为s1和s2
        int s1, s2;
        Select(HT, i - 1, s1, s2);
        HT[s1].parent = i; HT[s2].parent = i;
        HT[i].lchild = s1; HT[i].rchild = s2;//对左右子树的权值大小没有要求
        HT[i].weight = HT[s1].weight + HT[s2].weight;
    }
}
void Create_HT_Codelist(HuffmanTree HT, HuffmanCode& HC, int n) {
    HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));//分配n个字符编码的头指针
    char* cd = (char*)malloc(n * sizeof(char));//分配求编码的工作空间
    if ((!cd)||(!HC)) 
        exit(ERROR);
    cd[n - 1] = '\0';//编码最多有n-1位,最后一位定义位编码结束符
    int start;//编码结束符位置
    int c;
    for (int i = 1; i <= n; i++) {
        //逐个字符求哈夫曼编码
        start = n - 1;
        c = i;
        int f = HT[i].parent;
        for (; f != 0; c = f, f = HT[f].parent) 
            //从叶子到根逆向求编码
            if (HT[f].lchild == c)
                cd[--start] = '0';
            else 
                cd[--start] = '1';//左0右1
        HC[i] = (char*)malloc((n - start) * sizeof(char));//第0个不用,为第i个字符编码分配空间,'\0'也被存入
        if (!HC[i]) 
            exit(ERROR);
        strcpy_s(HC[i],n-start, &cd[start]);//从cd复制编码(串)到HC
    }
    free(cd);
}//Creat_HT_Codelist

Select函数是自己写的

void Select(HuffmanTree T, int n, int&s1, int&s2) {
    if (n <= 1) 
        exit(ERROR);
    unsigned int m1 = 4294967295; unsigned int m2 = 4294967295;//最大值
    for(int i=1;i<=n;i++)
        if ((T[i].weight < m1)&&(T[i].parent==0)) {
            m1 = T[i].weight;
            s1 = i;
        }
    for (int i = 1; i <= n; i++) 
        if ((T[i].weight < m2) && (i != s1) && (T[i].parent == 0)) {
            //要确保s1跟s2不相等,结果为s1对应元素的权值<=s2
            m2 = T[i].weight;
            s2 = i;
        } 
}

压缩函数

void Compress(char in_file_name[], char out_file_name[]) {
    FILE* f1,*f2;//f1为待压缩文件指针,f2为压缩后文件指针
    fopen_s(&f1, in_file_name, "rb");
    if (!f1)
        exit(ERROR);
   // fseek(f1, 0L, SEEK_SET);
    fseek(f1, 0L, SEEK_END);//将文件指针移到末尾
    unsigned long long FileSize = ftell(f1);//记录文件总字节数
    fseek(f1, 0L, SEEK_SET);//将指针移到开头
    unsigned int *FullWeight=(unsigned int *)malloc(256*sizeof(unsigned int));//记录256个字符是否存在及权值
    for (int i = 0; i < 256; i++)
        FullWeight[i] = 0;//初始化
    for (unsigned long long i = 0; i < FileSize; i++)//将文件的全部字符一个个读取出来
        FullWeight[(unsigned char)fgetc(f1)]++;//char转换为unsigned
    fclose(f1);
    int count=0;//字符种数
    for (int i = 0; i < 256; i++) 
        if (FullWeight[i]) 
            count++;
    unsigned char* CharSet = (unsigned char*)malloc(count * sizeof(unsigned char));//记录文件含有的字符
    unsigned int* WeightSet = (unsigned int*)malloc(count * sizeof(unsigned int));//记录字符的权值
    if (!CharSet || !WeightSet)
        exit(ERROR);
    int j=0;//最后的结果j=count-1
    for (int i = 0; i < 256; i++) 
        if (FullWeight[i]) {
            CharSet[j] = i;//字符表种字母顺序为26个英文字母的顺序
            WeightSet[j] = FullWeight[i];
            j++;
        }
    free(FullWeight);
    HuffmanTree HT;
    HuffmanCode HC;
    HuffmanCoding(HT, HC, WeightSet, count);
    fopen_s(&f2, out_file_name, "wb");//以二进制的形式写入
    if(!f2)
        exit(ERROR);
    fwrite(&count, sizeof(int), 1, f2);//写入字符个数
    //fwrite(&FileSize, sizeof(unsigned long long), 1, f2);//写入原文件字节总数
    //2n个哈夫曼树元素写入
    fwrite(HT, sizeof(HTNode), 2*count, f2);
    fwrite(CharSet, sizeof(unsigned char), count, f2);//写入字符表
    fwrite(WeightSet, sizeof(unsigned int), count, f2);//写入权值表
    //以下开始编码
    int offset = 8;//龙哥这里为7,记录一个字节内剩余位数
    int a = 0;//字符在CharSet里的位置
    unsigned char ReadByte;//读取的一个字节
    unsigned char TempByte=0;//暂时存储要写入的编码
    unsigned long long BitSize = 0;//记录位数,方便进行解压
    fopen_s(&f1, in_file_name, "rb");//以二进制的形式写入
    if (!f1)
        exit(ERROR);
    for (unsigned long long i = 0; i < FileSize; i++) {
        fread(&ReadByte, sizeof(unsigned char), 1, f1);
        a = 0;
        for (;; a++) 
            if (CharSet[a] == ReadByte) 
                break;
        for (int b = 0; HC[a+1][b]; b++) {
            //若读到'\0',则跳出
            //第一个没用,为HC[a+1][b]
            TempByte = (TempByte << 1) | (HC[a+1][b] - '0');//利用位或TempByte左移一位并写入一位
            BitSize++;
            offset--;
            if (offset == 0) {
                //字节8位已填满
                offset = 8;//重置为8位
                fwrite(&TempByte, sizeof(unsigned char), 1, f2);
                TempByte = 0;
            }
        }
    }
    if (offset != 8) {
        //若最后一个字节用不完,也要强行用到8位
        TempByte <<= offset;
        fwrite(&TempByte, sizeof(unsigned char), 1, f2);
    }
    fwrite(&BitSize, sizeof(unsigned long long), 1, f2);//将位数写在文件的最后
    fclose(f1);
    fclose(f2);
    free(HT);
    free(HC);
    free(CharSet);
    free(WeightSet);
    printf("已成功压缩!");
}

压缩后的文件内容按顺序为:文件的字符种数count、哈夫曼树HT、字符表CharSet、权值表WeightSet、压缩后内容、文件的总位数

解压函数

void Decompress(char in_file_name[], char out_file_name[]) {
    int count;
    FILE * f1, * f2;//f1为in,f2为out
   // HuffmanCode HC;
    HuffmanTree HT;
    unsigned long long BitSize;
    unsigned char* CharSet;
    unsigned int* WeightSet;
    fopen_s(&f1, in_file_name, "rb");//用“rb”
    if (!f1)
        exit(ERROR);
    fread(&count, sizeof(int), 1, f1);//读取字符种数count
    HT = (HuffmanTree)malloc(2 * count * sizeof(HTNode));
    CharSet = (unsigned char*)malloc(count * sizeof(unsigned char));
    WeightSet = (unsigned int*)malloc(count * sizeof(unsigned int));
    if ((!HT) || (!CharSet) || (!WeightSet))
        exit(ERROR);
    fread(HT, sizeof(HTNode), 2 * count, f1);//读取哈夫曼树
    fread(CharSet, sizeof(unsigned char), count, f1);//读取字符表
    fread(WeightSet, sizeof(unsigned int), count, f1);//读取权值表
  //  Create_HT_Codelist(HT, HC, count);//建立编码表
    fseek(f1, -1L * sizeof(unsigned long long), SEEK_END);//跳到末尾
    fread(&BitSize, sizeof(unsigned long long), 1, f1);//读取位数
    //跳过文件头
    fseek(f1, sizeof(int) + 2 * count * sizeof(HTNode) + count * sizeof(unsigned char) + count * sizeof(unsigned int), SEEK_SET);
    //fseek(Enc, (long)(sizeof(unsigned char) + 2 * count * sizeof(HTNode) + count * sizeof(unsigned char) + count * sizeof(unsigned int)), SEEK_SET);
    unsigned char TempByte = 0;
    unsigned char ReadByte;
    int offset = 8;
    int Index = 2 * count - 1;//根节点
    fopen_s(&f2, out_file_name, "wb");//打开目标文件
    if (!f2)
        exit(ERROR);
    fread(&ReadByte, sizeof(unsigned char), 1, f1);
    for (unsigned long long a = 0; a < BitSize; a++) {
        TempByte = 1 & (ReadByte >> 7);//位与判断ReadByte的位为1还是0
        if (TempByte)//从根结点开始寻找
            Index = HT[Index].rchild;
        else
            Index = HT[Index].lchild;
        if ((!HT[Index].lchild) && (!HT[Index].rchild)) {
            //遇到了根节点
            fwrite(&CharSet[Index - 1], sizeof(unsigned char), 1, f2);//由于HT有0号单元,故需-1
            Index = 2 * count - 1;
        }
        offset--;
        ReadByte = ReadByte << 1;//舍弃第一位
        if (offset == 0) {
            //字节的8位用完
            fread(&ReadByte, sizeof(unsigned char), 1, f1);
            offset = 8;
        }
    }
    free(CharSet);
    free(WeightSet);
    free(HT);
    fclose(f1);
    fclose(f2);
    printf("解压成功!");
}

主函数

主函数相当简单

int main() {
    puts("--------------------------欢迎使用Jay牌压缩程序---------------------------");
    puts("输入数字“1”进行压缩");
    puts("输入数字“2”进行解压");
    puts("输入数字“3”则退出");
    puts("--------------------------------------------------------------------------");
    int mark;
    char in_file_name[MAX_SIZE];//待压缩&&待解压文件名
    char out_file_name[MAX_SIZE];//解压后&&压缩后文件名
    for (;;) {
        printf("输入选项:");
        scanf_s("%d", &mark);
        getchar();//取掉回车字符
        switch (mark) {
        case 1:printf("请输入待压缩的文件路径:");//绝对还是相对路径?
            scanf_s("%s", in_file_name, MAX_SIZE); getchar();
            printf("请输入压缩后的文件路径:");
            scanf_s("%s", out_file_name, MAX_SIZE); getchar();
            Compress(in_file_name, out_file_name); break;
        case 2:printf("请输入待解压的文件路径:");
            scanf_s("%s", in_file_name, MAX_SIZE); getchar();
            printf("请输入压缩后的文件路径:");
            scanf_s("%s", out_file_name, MAX_SIZE);无getchar();
            Decompress(in_file_name, out_file_name); break;
        case 3:return 0;
        default:printf("请输入有效数字!");
        }
        putchar('\n');
    }
    return 0;
}

运行结果

w

  • 5
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是C语言实现哈夫曼树压缩解压文件的步骤和代码示例: 1. 定义哈夫曼树节点结构体和哈夫曼编码表结构体 ```c typedef struct { unsigned int weight; // 字符权重 unsigned int parent, lchild, rchild; // 父节点、左子节点、右子节点 } HTNode, *HuffmanTree; typedef char** HuffmanCode; // 动态分配数组存储哈夫曼编码表 ``` 2. 定义哈夫曼树相关函数 ```c // 选择权值最小的两个节点 void select(HuffmanTree HT, int n, int* s1, int* s2); // 建立哈夫曼树 void createHuffmanTree(HuffmanTree* HT, int n); // 生成哈夫曼编码 void createHuffmanCode(HuffmanTree HT, HuffmanCode* HC, int n); // 压缩文件 void compressFile(char* inputFile, char* outputFile, HuffmanCode HC); // 解压文件 void decompressFile(char* inputFile, char* outputFile, HuffmanTree HT, int fileLength); ``` 3. 实现哈夫曼树相关函数 ```c // 选择权值最小的两个节点 void select(HuffmanTree HT, int n, int* s1, int* s2) { int i; unsigned int min1 = UINT_MAX, min2 = UINT_MAX; // 初始化为最大值 for (i = 1; i <= n; i++) { if (HT[i].parent == 0) { // 只考虑未被选中的节点 if (HT[i].weight < min1) { min2 = min1; *s2 = *s1; min1 = HT[i].weight; *s1 = i; } else if (HT[i].weight < min2) { min2 = HT[i].weight; *s2 = i; } } } } // 建立哈夫曼树 void createHuffmanTree(HuffmanTree* HT, int n) { if (n <= 1) { return; } int m = 2 * n - 1; // 哈夫曼树总节点数 *HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); // 动态分配数组存储哈夫曼树 int i; for (i = 1; i <= n; i++) { // 初始化前n个节点 (*HT)[i].weight = 0; (*HT)[i].parent = 0; (*HT)[i].lchild = 0; (*HT)[i].rchild = 0; } for (i = n + 1; i <= m; i++) { // 初始化后m-n个节点 (*HT)[i].weight = 0; (*HT)[i].parent = 0; (*HT)[i].lchild = 0; (*HT)[i].rchild = 0; } for (i = 1; i <= n; i++) { // 输入前n个节点的权值 scanf("%d", &((*HT)[i].weight)); } int s1, s2; for (i = n + 1; i <= m; i++) { // 构造哈夫曼树 select(*HT, i - 1, &s1, &s2); (*HT)[s1].parent = i; (*HT)[s2].parent = i; (*HT)[i].lchild = s1; (*HT)[i].rchild = s2; (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight; } } // 生成哈夫曼编码 void createHuffmanCode(HuffmanTree HT, HuffmanCode* HC, int n) { *HC = (HuffmanCode)malloc((n + 1) * sizeof(char*)); // 动态分配数组存储哈夫曼编码表 char* code = (char*)malloc(n * sizeof(char)); // 分配临时存储编码的空间 code[n - 1] = '\0'; // 编码结束符 int i; for (i = 1; i <= n; i++) { // 逐个字符求哈夫曼编码 int start = n - 1; // 编码结束符位置 int c = i; // 从叶子节点开始向上回溯 int f = HT[i].parent; while (f != 0) { // 直到回溯到根节点 if (HT[f].lchild == c) { code[--start] = '0'; } else { code[--start] = '1'; } c = f; f = HT[f].parent; } (*HC)[i] = (char*)malloc((n - start) * sizeof(char)); // 分配存储编码的空间 strcpy((*HC)[i], &code[start]); // 复制编码 } free(code); // 释放临时存储编码的空间 } // 压缩文件 void compressFile(char* inputFile, char* outputFile, HuffmanCode HC) { FILE* in = fopen(inputFile, "rb"); // 以二进制方式打开输入文件 FILE* out = fopen(outputFile, "wb"); // 以二进制方式打开输出文件 unsigned char c; // 读入的字符 unsigned char buffer = 0; // 缓存区 int count = 0; // 缓存区中剩余的位数 while (fread(&c, sizeof(unsigned char), 1, in) == 1) { // 逐个字符读入 char* code = HC[c]; // 获取哈夫曼编码 while (*code != '\0') { // 逐位写入缓存区 if (*code == '1') { buffer = buffer | (1 << count); } count++; if (count == 8) { // 缓存区满了,写入输出文件 fwrite(&buffer, sizeof(unsigned char), 1, out); buffer = 0; count = 0; } code++; } } if (count > 0) { // 最后一个字节不足8位,补0写入输出文件 buffer = buffer << (8 - count); fwrite(&buffer, sizeof(unsigned char), 1, out); } fclose(in); // 关闭输入文件 fclose(out); // 关闭输出文件 } // 解压文件 void decompressFile(char* inputFile, char* outputFile, HuffmanTree HT, int fileLength) { FILE* in = fopen(inputFile, "rb"); // 以二进制方式打开输入文件 FILE* out = fopen(outputFile, "wb"); // 以二进制方式打开输出文件 unsigned char c; // 读入的字符 int p = 2 * fileLength - 1; // 指向哈夫曼树根节点 while (fread(&c, sizeof(unsigned char), 1, in) == 1) { // 逐个字节读入 int i; for (i = 0; i < 8; i++) { // 逐位解码 if ((c & (1 << (7 - i))) == 0) { p = HT[p].lchild; } else { p = HT[p].rchild; } if (HT[p].lchild == 0 && HT[p].rchild == 0) { // 到达叶子节点,写入输出文件 fwrite(&(HT[p].weight), sizeof(unsigned char), 1, out); p = 2 * fileLength - 1; // 指向哈夫曼树根节点 } } } fclose(in); // 关闭输入文件 fclose(out); // 关闭输出文件 } ``` 4. 调用函数进行压缩解压文件 ```c int main() { HuffmanTree HT; HuffmanCode HC; int n = 256; // 字符集大小 createHuffmanTree(&HT, n); // 建立哈夫曼树 createHuffmanCode(HT, &HC, n); // 生成哈夫曼编码 compressFile("input.txt", "output.dat", HC); // 压缩文件 decompressFile("output.dat", "output.txt", HT, n); // 解压文件 return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值