基于huffman树实现文件压缩

本文详细介绍了如何利用Huffman树实现文件压缩,包括统计字符出现次数、构建Huffman树、生成编码、压缩及解压缩五个步骤。在编码生成过程中,探讨了两种方法,比较了它们的优劣。在实际操作中,遇到如中文字符导致的数组越界问题,通过转换字符类型得以解决。同时,解压缩时需重建Huffman树,避免因丢失编码导致的问题。
摘要由CSDN通过智能技术生成

在上一篇博客中我们介绍了哈夫曼(huffman)树

本文将介绍一下如何用huffman树实现文件压缩。

首先,我们将文件压缩这个项目分为五个步骤:
1. 统计字符出现的次数。
2. 构建哈夫曼树
3. 生成哈夫曼编码
4. 压缩文件
5. 解压缩文件

这里写图片描述

以上就是文件压缩的基本思想,我们不难发现出现的越频繁,次数越多的字符,它的huffman编码就会越短,并且占用的空间也会越少,如果我们压缩的都是出现率特别高的重复字符,那么我们的压缩效果就会越好,相反,出现的字符越多并且很少有重复的那么压缩效率就会越低。

我们在写这五个步骤代码之前,需要先定义一个结构体,该结构是项目要用到的自定义类型,并且里面存放了字符,字符出现的次数,以及哈夫曼编码:

struct CharInfo
{
    char _ch;//字符
    long long _count;//出现的次数
    string _code;//huffman编码

    bool operator!=(const CharInfo& info)
    {
        return _count != info._count;
    }
    bool operator<(const CharInfo& info)
    {
        return _count < info._count;
    }
    CharInfo operator+(const CharInfo& info)
    {
        CharInfo tmp;
        tmp._count = _count + info._count;
        return tmp;
    }
};

那么我们就来具体的分析一下这五个步骤:

1.统计字符出现的次数。

我们要统计出要压缩的文件,每个字符出现的次数。首先我们要打开文件,并且开始读,将每个字符保存在_info数组中

代码:

     //打开文件
        FILE* fout = fopen(file, "rb");
        assert(fout);
        //统计字符出现的次数
        char ch = fgetc(fout);
        while (!feof(fout))
        {
            _infos[(unsigned char)ch]._count++;
            ch = fgetc(fout);//读当前字符,并且指针移向下一个字符
        }
2. 构建huffman树
//2,构建哈夫曼树
        CharInfo invalid;
        invalid._count = 0;
        //构建哈夫曼树的函数在上一篇博客有提到
        HuffmanTree<CharInfo> tree(_infos, 256, invalid);
3.生成哈夫曼编码

在这里有一个小小的问题:生成的hufffman编码存放在哪里?我们应该存在数组中,在数组中查找的快,所有我们在最开始的结构体中加入了string code,之所以我们弄为string是因为,静态的开辟太小,存不下,静态的开辟太大,多余的话又浪费了,所以使用string
生成哈夫曼编码有两种方法:

方法一:三叉链方法

如果这个树是三叉链,那么我们找到所有的叶子节点,从叶子结点往根节点走,由于这样出来的编码是倒着的,所以最后我们需要把保存下来的编码逆置一下

逆置的方法:
1. 头插(缺点:效率低,每次都需要挪动元素,效率为2*N(N-1))
2. 逆置函数rereverse()(效率高,推荐使用,效率为二分之N)
3. for循环

在本文我们使用的是函数的方法

void GetHuffmanCode(Node* root)
    {
        if (root == NULL)
        {
            return;
        }
        if (root->_left == NULL&&root->_right == NULL)
        {
            string &code = _infos[(unsigned char)root->_w._ch]._code;
            Node* cur = root;
            Node* parent = cur->_parent;
            while (parent)
            {
                //向左走0
                if (cur == parent->_left)
                {
                    code.push_back('0');
                }
                //向右走1
                else if (cur == parent->_right )
                {
                    code.push_back('1');
                }
                else
                {
                    break;
                }
                cur = parent;
                parent = cur->_parent;
            }

            reverse(code.begin(), code.end());

        }
        GetHuffmanCode(root->_left);
        GetHuffmanCode(root->_right);

    }
方法二:不用三叉链

我们多传入一个函数参数code

    void GetHuffmanCode(Node* root, string code)
    {
        if (root == NULL)
        {
            return;
        }
        if (root->_left == NULL&&root->_right == NULL)
        {
            //到叶子,生成编码
            _infos[(unsigned char)root->_w._ch]._code = code;
            return;
        }
        GetHuffmanCode(root->_left, code + '0');
        GetHuffmanCode(root->_right, code + '1');
        }

很多人会问什么是加0,而不用pushback,原因是因为如果我们pushback了,那么他就会把code传到下一层,

两种生成huffman编码的方法比起来,我们会选择,方法一,因为方法二的code消耗大,每次传给下一层&#

综合实验: 1. 问题描述 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。这要求在发送端通过一个编码系统对待传输数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站编写一个哈夫曼码的编/译码系统。 2. 基本要求 一个完整的系统应具有以下功能: (1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼,并将它存于文件hfmTree中。 (2) E:编码(Encoding)。利用已建好的哈夫曼(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。 (3) D:译码(Decoding)。利用已建好的哈夫曼文件CodeFile中的代码进行译码,结果存入文件Textfile中。 (4) P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。 (5) T:印哈夫曼(Tree printing)。将已在内存中的哈夫曼以直观的方式(比如)显示在终端上,同时将此字符形式的哈夫曼写入文件TreePrint 中。 3. 测试数据 用下表给出的字符集和频度的实际统计数据建立哈夫曼,并实现以下报文的编码和译码:“THIS PROGRAME IS MY FAVORITE”。 字符 A B C D E F G H I J K L M 频度 186 64 13 22 32 103 21 15 47 57 1 5 32 20 字符 N O P Q R S T U V W X Y Z 频度 57 63 15 1 48 51 80 23 8 18 1 16 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值