基于HuffmanTree的文件压缩及解压

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

个人博客传送门

HuffmanTree

定义

哈弗曼树是一种优化的二叉树,称为最优二叉树,是加权路径长度最小的二叉树。所谓权值在这里指的是节点中的数据。本文的哈弗曼树用数组提供数据,例如:arr[]={1,2,3,4,5,6}创建的哈弗曼树见下图:
hft
图中蓝色的节点值是数组arr提供,红色节点值是两个孩子节点值相加得到。

名词解释

节点的权值:权就相当于重要程度,通过一个具体的数字来表示
路径:在树中从一个节点到另一个节点的分支。
路径长度:一条路径上的分支数量。
树的路径长度:从树的根节点到每个节点的路径长度之和。
树的带权路径长度:树中各个叶子节点的路径长度*该叶子节点的权的和。

性质

1、根节点值是所有的叶子节点值相加得到
2、创建哈弗曼树的节点值全部在叶子节点上,而且只在叶子节点出现
3、哈夫曼树的加权路径长度是最小的,这个是因为权值大的节点离根节点近,权值小的节点离根节点远。

创建HuffmanTree

1、取出数组中最小的两个值,组成最小的一个分支,用两者的和作为两者的父亲节点并在原数组中替换两者。
2、依次循环直到数组所有符合要求的值被使用。
使用示例:arr[] = {1,2,3,4,5,6};
init

节点定义

哈弗曼树节点采用二叉链结构,包含关键值_val;指向左节点的指针_left;指向右节点的指针_right。

template <class T>
struct HuffmanTreeNode{
    T _val;
    HuffmanTreeNode<T>* _left;
    HuffmanTreeNode<T>* _right;

    HuffmanTreeNode(const T& val)
        :_val(val)
        ,_left(NULL)
        ,_right(NULL)
    {}
};
构造函数

构造函数提供了两个,一个是空的构造函数,防止因为没有默认构造函数产生错误。另一个是带参的构造函数。

参数解释

arr是一个数组名,用来提供创建哈弗曼树的数值来源;size是数组的大小,这个大小在构建最小堆的时候会用到;invalid指的是非法值,如果数组中有一些特定的值,不希望插入到哈弗曼树中,用这个值来判断。一般来说,所有的非法值是相同的。

构建的思想

构建哈夫曼树的方式在上面已经提到,但是如何用代码来实现呢?这里可以借用最小堆来实现。如果不太熟悉最小堆可以看这个传送门。每次取最小堆的堆顶元素第一次是作为左节点,第二次作为右节点。同时将两者出堆,接着用两者的关键值创建一个父亲节点入堆。利用循环就可以创建出来哈弗曼树了。至于为什么要用最小堆,根据前述的方法,我们需要每次取数组中的最小的两个值来构建新的节点,用最小堆的话可以分两次取堆顶数据,然后让创建的新的节点再入堆。最小堆会自动调整,不需要人为参与修改。而且堆的存储机制就是动态增长的数组,天然符合这里利用数组传参的要求。其中这里传入给最小堆的数据类型是节点指针,为什么是节点指针很好理解。如果是节点本身,那么我们就没有办法将之前创建的内容链接在一起,见下图:
Node*
为什么不用节点的引用呢?引用的话不太好,如果是引用,可能出现节点结构太大的情况,这样开销有点大,不划算。这里还需要解释一下,需要自己创建一个比较仿函数,因为Node是一个自定义类型,在创建最小堆的时候是没有办法知道比较方式的,仿函数如下:(其中为了书写方便,在HuffmanTree类中定义了节点Node)

typedef HuffmanTreeNode<T> Node;

struct NodeCompare{
    //const Node* left, const Node* right
    //wrong
    bool operator()(Node* left, Node* right){
        return left->_val < right->_val;
    }
};

这里需要注意的是,仿函数的参数不能够定义为const对象。需要修改。
构建的代码如下:注意循环结束的条件是最小堆中只有一个元素,因为此时已经构建完成。

HuffmanTree()
    :_root(NULL)
{}
HuffmanTree(T* arr, size_t size, const T& invalid){
    //注意这里使用的是Node*
    Heap<Node*, NodeCompare> minheap;
    for(size_t i = 0; i < size; ++i){
        if(arr[i] != invalid){
            //Node*
            minheap.Push(new Node(arr[i]));
        }
    }
    //minheap.Size()>1
    while(minheap.Size() > 1){
        Node* left = minheap.Top();
        minheap.Pop();
        Node* right = minheap.Top();
        minheap.Pop();
        Node* parent = new Node(left->_val + right->_val);
        parent->_left = left;
        parent->_right = right;
        minheap.Push(parent);
    }
    _root = minheap.Top();
}
析构函数

析构函数通过递归实现,内部调用Destroy实现。

//destructor
~HuffmanTree(){
    Destroy(_root);
    _root = NULL;
}
void Destroy(Node* root){
    if(root){
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
    }
}
获取根节点函数
//GetRoot
Node* GetRoot(){
    return _root;
}
补充

因为没有实现拷贝构造和赋值运算符的重载,所以将他们都声明为私有成员变量。防止错误使用导致未知错误

文件压缩及解压缩

有了哈弗曼树,我们就可以利用哈弗曼树创建哈夫曼编码,通过哈夫曼编码可以实现文件压缩的功能。

哈夫曼编码

所谓哈夫曼编码是在哈弗曼树的基础上,定义向左的路径为零,向右的路径为一。这样定义以后,每一个叶子节点都会有一个唯一的编码值。而重要的是,在哈弗曼树中,所有叶子节点就是我们用来构建哈弗曼树的原值。使用示例:arr[] = {1,2,3,4,5,6};如下图:
哈夫曼编码
需要注意的是,哈夫曼编码不是唯一的,就算是同样的原值,因为插入的顺序不一样会导致不同的编码,但是毋庸置疑的是,每一个叶子节点一定会有唯一的编码值。

压缩思路

1、扫描文件内容,统计文件中字符出现的次数。
2、利用文件出现次数构建哈弗曼树。
3、创建哈夫曼编码。
4、将创建哈弗曼树的字符及其对应出现次数写入压缩文件中,用于解压使用。
5、将哈夫曼编码替换原子符写入压缩文件。

压缩原理

举例:文件test.txt中内容:aaaaaabbbbbccccdddeef
其中字符出现的次数统计:a->6;b->5;c->4;d->3;e->2;f->1。利用次数构建的哈弗曼树和编码如下:
example
图中黑色字体是出现的字符,蓝色数字是对应出现的次数,红色字体是构建时两字符出现次数相加之和。为什么通过哈夫曼编码可以压缩文件?我们通过编码将原字符替换,只有当编码的长度超过一个字节的时候(也就是八个比特位)才会比替换之前的字符大小要大。但是出现次数多的字符的编码都十分的短,出现次数少的字符编码才会超过一个字节。这样抵消之下,肯定是相比于压缩之前文件大小要小。上例子中,写入压缩文件的压缩编码如下:
比较

压缩与解压代码分析
结构体分析

结构体CharInfo是用来存储字符、字符出现的次数、字符的编码三者的结构类型。其中将_count定义为long long类型,防止字符出现的次数超过了整型表示范围。_code用string类型;通过这个结构体可以将三者紧紧地联系在一起。其中对该结构体重载了!=、+、< 这三个运算符。在代码中都会用到。

typedef long long LongType;
struct CharInfo{
    char _ch;
    LongType _count;
    string _code;

    //for invalid
    bool operator!=(const CharInfo& info){
        return _count != info._count;
    }

    //for minheap.parent
    CharInfo operator+(const CharInfo& info){
        CharInfo tmp;
        tmp._count = _count + info._count;
        return tmp;
    }

    //for compare
    bool operator<(const CharInfo& info){
        return _count < info._count;
    }
};
构造函数

本文使用的文件压缩类类名为FileCompress。类的私有成员是一个CharInfo类型的数组。其大小是256,因为字符包括汉字一共只有256个。所以直接指定数组大小就好。

CharInfo _infos[256];

构造函数直接将数组的下标赋值给结构体CharInfo的_ch成员,因为字符型数据就是通过整型转换过去的。这样就相当于给数组的每一个元素都指定好了对应字符。例如:_infos[97]对应字符’a’。

FileCompress(){
    for(size_t i = 0; i < 256; ++i){
        _infos[i]._ch = i;
        _infos[i]._count = 0;
        _infos[i]._code = " ";
    }
}
压缩函数

压缩函数分为五步,步骤在代码中详细的分析,这里分别说一下每个步骤注意的点:
1、统计。统计时,要注意用二进制方式打开文件,用二进制模式打开的文件不会对任何字符有转义的动作,文本模式下,window操作系统会将’\n’和’\r’进行编码转换。而且写入的时候,也需要用二进制方式写入,否则会出现压缩没有问题,但是解压的时候提前结束的情况。
2、构建哈弗曼树。定义非法值,其字符个数为零个。
3、构建哈夫曼编码调用函数。
4、写入构建哈弗曼树的字符和对出现次数。调用fopen函数,要将string类转换为char*格式;创建一个没有string类的临时结构体,因为构建哈弗曼树只需要ch和count;将构件用的源写入之后,再写入一个标志位,用字符个数为-1表示,用于跟替换的编码相互隔开。
5、用编码将字符替换。在进行这一步之前,将文本指针指向文章开头。因为统计字数的时候,文本指针已经指向了文末。替换的步骤是:从源文件中获取一个字符,通过该字符获得之前已经编好的哈夫曼编码,获取编码的每一个位,如果编码为’1’,将value当前位置为1;反之,置为0。依次进行,当该字符的编码全部用完,获取下一个字符,一直到value的八个位都替换完成, 将value写入压缩文件中。为什么要到一个字节才写入,这个是因为函数能写入的最小的单位是字节。

void Compress(const char* file){
//fopen的打开方式,用二进制方式打开
    FILE* fout = fopen(file, "rb");
    assert(fout);
    //1、统计文章字符出现的次数
    //fgetc的返回值是整型
    int ch = fgetc(fout);
    while(ch != EOF){
        ++ _infos[(unsigned char)ch]._count;
        ch = fgetc(fout);
    }
    //2、构建哈夫曼树
    CharInfo invilad;
    invilad._count = 0;
    HuffmanTree<CharInfo> tree(_infos, 256, invilad);
    //code不要初始化,不然会出现编码错误
    string code;
    //3、构建哈夫曼编码
    GenerateHuffmanTreeCode(tree.GetRoot(), code);
    //4、写入构建哈弗曼树的字符和对出现次数
    string compressfile = file;
    compressfile += ".huffman";
    FILE* fin = fopen(compressfile.c_str(), "wb");
    assert(fin);

    for(int i = 0; i < 256; ++i){
        if(_infos[i]._count != 0){
            TmpCharInfo info;
            info._ch = _infos[i]._ch;
            info._count = _infos[i]._count;
            fwrite(&info, sizeof(info), 1, fin);
        }
    }
    //设置判断位,让其跟压缩内容分割开来
    TmpCharInfo info;
    info._count = -1;
    fwrite(&info, sizeof(info), 1, fin);

    //5、用编码将字符替换。
    fseek(fout, 0, SEEK_SET);
    ch = fgetc(fout);
    //value是用来写入到压缩文件中的一个字节
    char value = 0;
    //pos两个作用,一个是当前替换的位置,另一个是用来判断是不是已经满八个比特位
    size_t pos = 0;
    while(ch != EOF){
        string& code = _infos[(unsigned char)ch]._code;
        for(size_t i = 0; i < code.size(); ++i){
            if(code[i] == '1'){
                value |= (1 << pos);
            }
            else if(code[i] == '0'){
                value &= ~(1 << pos);
            }
            else{
                assert(false);
            }
            ++ pos;
            if(pos == 8){
                fputc(value, fin);
                value = pos = 0;
            }
        }
        ch = fgetc(fout);
    }
    //特殊处理未满八个位也将剩下的字符全部写入。
    //正是因为这一步,才会有说上图中用来填充的五个零的出现。
    if(pos > 0){
        fputc(value, fin);
    }

    fclose(fout);
    fclose(fin);
}
构建哈夫曼编码函数

这个函数利用递归实现,如果当前走向哈弗曼树的左子树code+0,如果是右子树code+1。如果当前节点是叶子节点那么修改对应字符的_code为code的值。因为这里用的code是值传递,当递归函数返回上一层的时候,当前层的修改不会被带回去。
递归
如上图中的根节点6,它的code是空字符,第一层递归的时候,进入节点3,此时的code是值传递,会进行一次值拷贝,节点3的code是“0”,再下一层,进入节点1,同样是值拷贝,节点1的code是“00”。此时节点1的叶子节点,写入字符’1’的_code为字符串”00”。这个就是它的编码。然后退回到节点3,它的code依然是“0”。然后再进入到节点2,它的code是“01”,叶子节点,写入字符’2’的_code是字符串”01”。这个是他的编码。

//GenerateHuffmanTreeCode
void GenerateHuffmanTreeCode(const HTreeNode* root, string code){
    if(root == NULL)
        return;
    if(root->_left == NULL && root->_right == NULL){
        //change _infos
        _infos[(unsigned char)(root->_val._ch)]._code = code;
        return;
    }
    GenerateHuffmanTreeCode(root->_left, code+'0');
    GenerateHuffmanTreeCode(root->_right, code+'1');
}
解压函数

解压函数的思路:
1、从压缩文件中读取构建哈弗曼树的字符和次数
2、构建哈夫曼树
3、从压缩文件中读取编码,结合哈弗曼树将编码翻译为字符写入解压文件中。

//unCompress
void UnCompress(const char* file){
    //获取压缩文件名字,同时设定解压文件的文件名
    string uncompressfile = file;
    size_t pos = uncompressfile.rfind('.');
    if(pos == string::npos){
        perror("pos wrong");
        exit(1);
    }
    uncompressfile.erase(pos);
    uncompressfile += ".unhuffman";

    //解压
    FILE* fin = fopen(uncompressfile.c_str(), "wb");
    assert(fin);
    //从压缩文件中获取构建哈弗曼树的字符和对应的出现的次数。
    FILE* fout = fopen(file, "rb");
    assert(fout);
    TmpCharInfo info;
    fread(&info, sizeof(info), 1, fout);
    while(info._count != -1){
        _infos[(unsigned char)info._ch]._ch = info._ch;
        _infos[(unsigned char)info._ch]._count = info._count;
        fread(&info, sizeof(info), 1, fout);
    }
    //创建哈弗曼树,用来翻译编码使用
    CharInfo invalid;
    invalid._count = 0;
    HuffmanTree<CharInfo> tree(_infos, 256, invalid);
    HTreeNode* root = tree.GetRoot();
    HTreeNode* cur = root;
    LongType num = root->_val._count;
    int ch = fgetc(fout);
    //解决文件中只有一个字符的情况
    if(root->_left == NULL){
        while(num--){
            fputc(root->_val._ch, fin);
        }
    }
    else{
        while(ch != EOF){
            for(size_t i = 0; i < 8; ++i){
                //按照编码规则,遇到1向右走,遇到0向左走。
                if(((unsigned char)ch & (1 << i)) == 0){
                    cur = cur->_left;
                }
                else{
                    cur = cur->_right;
                }
                //叶子节点写入翻译字符
                if(cur->_left == NULL && cur->_right == NULL){
                    //限制翻译的字符的个数,防止将补充的字符也翻译。
                    if(num-- == 0){
                        break;
                    }
                    fputc(cur->_val._ch, fin);
                    //指向root,解压下一个字符
                    cur = root;
                }
            }
            //当前读取的一个字节已经用完,继续解压下一个字节
            ch = fgetc(fout);
        }
    }

    fclose(fin);
    fclose(fout);
}

注意:
1、读取编码转换字符的方法是:获取到的编码一定是一个字节的,但是其中可能包含了多个编码,此时结合哈弗曼树,如果编码遇到的是’1’,在哈弗曼树中向右子树中走,如果是’0’,在哈夫曼中向左子树走。一直遇到叶子节点的时候,表示此时已经翻译成功一个字符,将该字符写入解压文件中。同时将指向节点的指针重新指向根节点,此时需要解压下一个字符。
2、如果原文件中只有一个字符,此时是构不成哈弗曼树的,需要特殊处理这种情况。
3、需要控制翻译的字符的个数。因为压缩文件中最后几个位的字符可能是当初为了写入一个字节而填充的,这些并不是源文件中含有的字符。因此这些字符不能被翻译。根据哈弗曼树的性质可以知道,根节点的值就是所有叶子节点的关键值之和。所以可以获取根节点的关键值,每翻译一个字符,就将该关键值减一,当关键值为零的时候表示原文件中所有的字符都已经被翻译出来了,此时不管是否还能读取到字符,都应该直接跳出写入字符的循环,防止写入多余的无用值。

代码

//Heap.h
Heap

//Huffman.h
#pragma once
#include "Heap.h"

template <class T>
struct HuffmanTreeNode{
    T _val;
    HuffmanTreeNode<T>* _left;
    HuffmanTreeNode<T>* _right;

    HuffmanTreeNode(const T& val)
        :_val(val)
        ,_left(NULL)
        ,_right(NULL)
    {}
};

template <class T>
class HuffmanTree{
    typedef HuffmanTreeNode<T> Node;
    struct NodeCompare{
        //const Node* left, const Node* right
        //wrong
        bool operator()(Node* left, Node* right){
            return left->_val < right->_val;
        }
    };
public:
    //constructor
    HuffmanTree()
        :_root(NULL)
    {}
    HuffmanTree(T* arr, size_t size, const T& invalid){
        Heap<Node*, NodeCompare> minheap;
        for(size_t i = 0; i < size; ++i){
            if(arr[i] != invalid){
                //Node*
                minheap.Push(new Node(arr[i]));
            }
        }
        //minheap.Size()>1
        while(minheap.Size() > 1){
            Node* left = minheap.Top();
            minheap.Pop();
            Node* right = minheap.Top();
            minheap.Pop();
            Node* parent = new Node(left->_val + right->_val);
            parent->_left = left;
            parent->_right = right;
            minheap.Push(parent);
        }
        _root = minheap.Top();
    }
    //destructor
    ~HuffmanTree(){
        Destroy(_root);
        _root = NULL;
    }
    void Destroy(Node* root){
        if(root){
            Destroy(root->_left);
            Destroy(root->_right);
            delete root;
        }
    }
    //GetRoot
    Node* GetRoot(){
        return _root;
    }
private:
    HuffmanTree(const HuffmanTree<T>& huf);
    Node*& operator=(const HuffmanTree<T>& huf);
    Node* _root;
};

void TestHuffmanTree(){
    int arr[] = {0,1,2,3,4,5,6,7,8,9};
    size_t Size = sizeof(arr)/sizeof(arr[0]);
    HuffmanTree<int> hp(arr, Size, arr[0]);
    cout << hp.GetRoot()->_val << endl;
}


//Compress.h
#pragma once
#include <string>
#include <cstdio>
#include <cstring>
#include <assert.h>
#include "HuffmanTree.h"


typedef long long LongType;
struct CharInfo{
    char _ch;
    LongType _count;
    string _code;

    //for invalid
    bool operator!=(const CharInfo& info){
        return _count != info._count;
    }

    //for minheap.parent
    CharInfo operator+(const CharInfo& info){
        CharInfo tmp;
        tmp._count = _count + info._count;
        return tmp;
    }

    //for compare
    bool operator<(const CharInfo& info){
        return _count < info._count;
    }
};

class FileCompress{
    typedef HuffmanTreeNode<CharInfo> HTreeNode;
    struct TmpCharInfo{
        unsigned char _ch;
        LongType _count;
    };
public:
    //constructor
    FileCompress(){
        for(size_t i = 0; i < 256; ++i){
            _infos[i]._ch = i;
            _infos[i]._count = 0;
            _infos[i]._code = " ";
        }
    }

    //Compress
    void Compress(const char* file){
        FILE* fout = fopen(file, "rb");
        assert(fout);
        int ch = fgetc(fout);
        while(ch != EOF){
            ++ _infos[(unsigned char)ch]._count;
            ch = fgetc(fout);
        }
        //create HuffmanTree
        CharInfo invilad;
        invilad._count = 0;
        HuffmanTree<CharInfo> tree(_infos, 256, invilad);
        string code;
        GenerateHuffmanTreeCode(tree.GetRoot(), code);


        //write
        string compressfile = file;
        compressfile += ".huffman";
        FILE* fin = fopen(compressfile.c_str(), "wb");
        assert(fin);

        //write source
        for(int i = 0; i < 256; ++i){
            if(_infos[i]._count != 0){
                TmpCharInfo info;
                info._ch = _infos[i]._ch;
                info._count = _infos[i]._count;
                fwrite(&info, sizeof(info), 1, fin);
            }
        }
        //flag of the end of source
        TmpCharInfo info;
        info._count = -1;
        fwrite(&info, sizeof(info), 1, fin);

        //write code
        fseek(fout, 0, SEEK_SET);
        ch = fgetc(fout);
        char value = 0;
        size_t pos = 0;
        while(ch != EOF){
            string& code = _infos[(unsigned char)ch]._code;
            for(size_t i = 0; i < code.size(); ++i){
                if(code[i] == '1'){
                    value |= (1 << pos);
                }
                else if(code[i] == '0'){
                    value &= ~(1 << pos);
                }
                else{
                    assert(false);
                }
                ++ pos;
                if(pos == 8){
                    fputc(value, fin);
                    value = pos = 0;
                }
            }
            ch = fgetc(fout);
        }
        if(pos > 0){
            fputc(value, fin);
        }

        fclose(fout);
        fclose(fin);
    }

    //GenerateHuffmanTreeCode
    void GenerateHuffmanTreeCode(const HTreeNode* root, string code){
        if(root == NULL)
            return;
        if(root->_left == NULL && root->_right == NULL){
            //change _infos
            _infos[(unsigned char)(root->_val._ch)]._code = code;
            return;
        }
        GenerateHuffmanTreeCode(root->_left, code+'0');
        GenerateHuffmanTreeCode(root->_right, code+'1');
    }

    //unCompress
    void UnCompress(const char* file){
        //change the name of file
        string uncompressfile = file;
        size_t pos = uncompressfile.rfind('.');
        if(pos == string::npos){
            perror("pos wrong");
            exit(1);
        }
        uncompressfile.erase(pos);
        uncompressfile += ".unhuffman";

        //uncompress
        //open fin
        FILE* fin = fopen(uncompressfile.c_str(), "wb");
        assert(fin);
        //read to get ch and count
        FILE* fout = fopen(file, "rb");
        assert(fout);
        TmpCharInfo info;
        fread(&info, sizeof(info), 1, fout);
        while(info._count != -1){
            _infos[(unsigned char)info._ch]._ch = info._ch;
            _infos[(unsigned char)info._ch]._count = info._count;
            fread(&info, sizeof(info), 1, fout);
        }
        //create HuffmanTree
        CharInfo invalid;
        invalid._count = 0;
        HuffmanTree<CharInfo> tree(_infos, 256, invalid);
        HTreeNode* root = tree.GetRoot();
        HTreeNode* cur = root;
        LongType num = root->_val._count;
        int ch = fgetc(fout);
        //olny root
        if(root->_left == NULL){
            while(num--){
                fputc(root->_val._ch, fin);
            }
        }
        //more than root
        else{
            while(ch != EOF){
                for(size_t i = 0; i < 8; ++i){
                    if(((unsigned char)ch & (1 << i)) == 0){
                        cur = cur->_left;
                    }
                    else{
                        cur = cur->_right;
                    }

                    if(cur->_left == NULL && cur->_right == NULL){
                        if(num-- == 0){
                            break;
                        }
                        fputc(cur->_val._ch, fin);
                        cur = root;
                    }
                }
                ch = fgetc(fout);
            }
        }

        fclose(fin);
        fclose(fout);
    }
protected:
    CharInfo _infos[256];
};

void TestFileCompress(){
    FileCompress fc;
    FileCompress fcu;
    fc.Compress("file");
    fcu.UnCompress("file.huffman");
}
Qt实现哈夫曼编码解压缩软件详解 诸位既然点开了本帖,相信对此问题已有初步了解,哈夫曼的原理不再赘述,我们开门见山,直入主题。 一、概要设计 问题拆解:设计一个基于哈夫曼编码解压缩软件,这个问题我认为可以分解为以下几个子问题: 读取传入文件,进行字符权重统计 将出现的字符放入哈夫曼结点,构建哈夫曼,获取哈夫曼编码 将编码相关信息写入压缩后的文件,再将传入文件的每个字符按照哈夫曼编码转换,每8个二进制位作为一个字节传入压缩后的文件 解压部分:将传入的已压缩文件进行文件流读取,获取编码信息进行还原 根据这几个子问题的思路顺序,我们逐 阅读详情

相关推荐

基于哈夫曼编码的图像压缩算法实现

哈夫曼编码是一种常用的压缩编码算法,采用变长码编码,属于无损压缩算法的一种,它可以将一组符号编码成对应的二进制编码,使得编码后的数据具有最短的码长,也叫霍夫曼编码。在图像压缩中,哈夫曼编码可以被用来表示像素信息,从而实现对图像的压缩。 具体来说,哈夫曼编码的基本思路是将出现频率较高的符号用较短的编码表示,而出现频率较低的符号则用较长的编码表示。对于图像数据,我们可以将出现频率较高的像素值编码为较短的码字,而对于出现频率较低的像素值,则编码为较长的码字。

qq_60737285的博客 5241

基于哈夫曼编码文件压缩解压程序的C语言实现

利用无失真信源编码方法中的哈夫曼编码进行程序设计实践,实现文件压缩解压操作。

哈夫曼编码实现图片压缩解压

整体思路: 读原文件,获得权值列表,生成哈夫曼,遍历生成哈夫曼编码。 写入压缩文件,后缀.huf,先写入文件头(包括文件类型,权值列表,文件长度,以便于从压缩文件中再次生成哈夫曼编码解压,也可以用其他的方式,比如直接将哈夫曼编码以先序遍历的方式写入压缩文件的开始,代码中有体现)。 将原文件编码后写入压缩文件。 读取压缩文件,生成哈夫曼,每次读取到0则向左子移动,读取到1向右子移动,直到遇到叶子结点,将叶子结点存储的编码写入解压文件。 值得注意的的是,权值数组的运作。图片文件按二进制读取,一.

frankkyle的博客 6238

基于哈夫曼文件压缩解压QT可视化界面)

大二课设作业。使用哈夫曼进行文件编码,从而实现文件压缩。整个程序基于QT5.12进行操作,并且实现了可视化界面。包括编码,解码。如果有什么问题,可私戳了解。

数据结构与算法课程设计——基于哈夫曼编码文件压缩

编写一个基于哈夫曼编码文件压缩/解压系统,一个完整的系统应具有以下基本功能: (1) 初始化。给出需要压缩文件sourcefile.txt, ,建立哈夫曼,并将哈夫曼或者字符的编码映射存到文件中,文件名自己定。 (2) 压缩编码。利用已建好的哈夫曼,对文件sourcefile.txt的正文进行编码,然后将结果存入文件compressed。可以是01字符序列或者8个01为一个字节的二进制文件,见进阶功能(4)的说明。 (3) 解压缩。读入compressed文件,利用已建好的哈夫曼(在内存中)或者读

加载中....... 6142

基于HuffmanTree文件压缩解压

目录   HuffmanTree 定义 名词解释 性质 创建HuffmanTree 定义HuffmanTree节点 构造函数 参数解释 构建思想 析构函数 获取根节点 文件压缩解压哈夫曼编码 压缩思路 压缩文件格式 解压缩 完整的项目 HuffmanTree 定义 哈弗曼是一种优化的二叉,称为最优二叉,是加权路径长度最小的二叉。所谓权值在这里...

yulong__li的博客 575

C++项目:基于HuffmanTree实现文件压缩解压

系列文章目录 文章目录系列文章目录前言一、文件压缩1.文件压缩的概念2.为什么需要压缩3.压缩的分类4.压缩的方法二、HuffmanTree文件压缩解压缩1.HuffmanTree的概念2.HuffmanTree的构建3.文件压缩1.统计源文件中每个字符出现的频数2.根据统计的结果创建HuffmanTree3.借助Huffman获取每个字节的编码4.使用获取到的字节编码对源文件进行改写,对源文件每个字节替换成huffman编码4.文件解压缩1.解压缩需要获取的信息2.从压缩文件读取解压缩需要用到的信息

森明帮大于黑虎帮的博客 4614

项目:文件压缩(huffman)

原理: 将每个字符转换成对应的编码,而编码都是由01组成,那么就可以用比特位来表示编码对应的字符,从而实现文件压缩;同样,再根据压缩的步骤反推,即可实现解压缩 思路: 1,统计文件中各个字符出现的次数 定义一个哈希表,用直接定址法,统计各个字符出现的次数; 要注意的是:字符的大小是-127~128,而哈希表的范围是0~255,所以使用直接定址法的时候需强转成无符号 2,构建哈夫...

summer初夏的博客 383

C++实现的Huffman编码压缩解压缩软件

Huffman编码是一种广泛应用于数据压缩的编码方式。基本思想是根据每个字符在待编码文件中出现的频率来构建最优前缀码。出现频率高的字符采用较短的编码,而出现频率低的字符则使用较长的编码。这种编码方式可以保证整体的编码长度最小,从而达到压缩数据的目的。

weixin_33173126的博客 807

C语言实现Huffman的编码解码

说起 Huffman的算法原理其实很简单,难在实现过程中对细节的控制,比如 字串流转换成 比特流, 比特流转换回 字串流,这类操作极易出错;再比如要使 解码过程效率更高,需要让 游标指针在逐个获取 比特位的过程中高效地从根节点移动到目标节点,从而获取目标节点对应的解码字符;再就是针对解码所必须的 字符集频率表如何设计才能最大限度减少体积。总之我的体会就是,若要亲手实现一个各方面鲁棒性良好的 Huffman Code Program其过程并不那么轻松。...

code helper 1278

VC++实现Huffman压缩算法:从原理到工程实践详解

数据压缩是计算机科学中优化存储与传输效率的核心技术,其基本原理是通过特定编码方式减少数据的冗余表示。Huffman编码作为一种经典的无损压缩算法,基于字符出现频率构建最优前缀码,高频字符分配短码,低频字符分配长码,从而实现压缩。该技术在ZIP、JPEG等格式中广泛应用,其工程价值在于平衡压缩率与编解码效率。在文件压缩、网络传输等场景中,Huffman算法能有效减少数据体积。本文以VC++开发环境为例,详细解析如何实现一个完整的Huffman压缩工具,涵盖频率统计、建、位流打包等关键步骤,并针对文件尾部处理

weixin_33985507的博客 306

文件压缩

1.需求分析 利用小堆,huffman编码,文件流操作,二进制文件的读写实现对普通文件压缩解压过程。 2.能力要求 A.熟悉对文件的读写操作。 B.熟悉小堆的原理。 C.熟悉HuffmanTree实现原理、 D.会编码的获取。 E.对编码信息处理存储。 F.最重要一点,要理解压解压缩的过程。 3.

Armypopy的博客 422

基于哈夫曼编码文件压缩项目(2),2024年最新4个改变你编程技能的小技巧

以下是我总结完成过程中注意的一些问题:1、创建哈夫曼时,使用优先级队列来保存字符出现的频次信息时,优先级队列的比较方式需要自己进行给出,使用仿函数的方法让比较方式为每个节点中的权值。2、在使用结构体来创建哈夫曼时,需要对+,>,==,!=,进行重载,重载的目的:让编译器知道权值中的count之间的比较方式3、在构建哈夫曼时,发现数组中还存放着许多字符出现次数为0的节点,需要进行过滤,设置第二个参数invalid来过滤权值为0的节点。

2401_84183669的博客 886

基于哈夫曼编码文件压缩项目,完美讲解内存缓存LruCache实现原理

以下是我总结完成过程中注意的一些问题:1、创建哈夫曼时,使用优先级队列来保存字符出现的频次信息时,优先级队列的比较方式需要自己进行给出,使用仿函数的方法让比较方式为每个节点中的权值。2、在使用结构体来创建哈夫曼时,需要对+,>,==,!=,进行重载,重载的目的:让编译器知道权值中的count之间的比较方式3、在构建哈夫曼时,发现数组中还存放着许多字符出现次数为0的节点,需要进行过滤,设置第二个参数invalid来过滤权值为0的节点。

2401_84183669的博客 644

C语言:用哈夫曼进行文本文件(英文)文件压缩

以此字符串(长为100)为例:AAAAAEEEEEEEEEEEEEEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDDDDDCCCCCCCFFFFFFFFFFFFFFFFFFFFFFFGGGHHHHHHHHHHH。

Bet_on_me的博客 474

基于哈夫曼编码文件压缩工具

本文旨在介绍基于哈夫曼编码文件压缩解压缩工具设计思路与代码实现

2401_83740084的博客 1031

项目(1)——文件压缩

项目——文件压缩 一、简历上写的 测试用例:77M大小的一个文本文件 压缩: 33秒 解压缩: 6秒 文件压缩 项目简介: 对文件进行无损压缩,节省空间,用在网络传输中,节省流量传输宽带 开发环境: Linux/Windows,C++,VS2013,vim,gdb,g++,make 主要技术: HuffmanTree,Heap,字符串操作,位操作 项目描述: 压...

yx20130919的博客 722

哈夫曼实现文件压缩解压

利用哈夫曼实现文件压缩解压压缩: 1、统计出文件中相同字符出现的次数 2、获取哈夫曼编码 次数作为权值构建哈夫曼 3、重新编码,写回压缩文件 保存头文件: 源文件后缀 编码信息的行数 每个字符的权

深海一诺 1万+

文本压缩版本三

Heap.h #pragma once #include <vector>#include <algorithm> using namespace std; /********************************************************************************* @File name: Heap.h* @A...

weixin_30670925的博客 69
上一篇: 堆的解析和实现
下一篇: vector二维数组中的查找
mac_timmy
博客等级 码龄9年 7粉丝 51原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值