哈弗曼编码实现文件压缩和解压缩

原理

哈弗曼编码的本质是将文件中出现频率越高的字符用越短的二进制码代替从而达到节省空间的目的。为了达到这个目的,需要构建哈夫曼树。
哈弗曼树的构建过程为:
1. 将源文件所有字符及其出现次数构建哈弗曼节点
2. 将权值最小的两个节点作为左右叶子节点组成一个新的哈弗曼节点来替代原哈弗曼节点集合中的这两个节点
3. 重复步骤2直至只剩一个哈弗曼节点,则该节点为哈夫曼树
而每个节点的哈弗曼编码为哈夫曼树的根节点到该节点所经过的路径编码:每经过一次左孩子则在后面添加编码“0”,每经过一次右孩子则在后面添加编码“1”,直至到达该节点。此时累计的0和1组成的编码即为该节点的哈弗曼编码。

实现

辅助方法

以下辅助结构体和方法会在压缩和解压缩过程中用到

struct Node{//哈弗曼节点
    int sortValue;
    char value;
    string huffmanValue;
    Node* left;
    Node* right;
};
struct SortByValue {//对哈弗曼节点升序排列
    bool operator()(const Node* lhn, const Node* rhn) {
        return lhn->sortValue < rhn->sortValue;
    }
};
void setHuffmanValue(Node* node,string s){//对哈夫曼树的每个节点的哈弗曼编码进行赋值,左孩子加“0”,右孩子加“1”
    node->huffmanValue += s;
    if (node->left!=nullptr)
        setHuffmanValue(node->left, node->huffmanValue + "0");
    if (node->right != nullptr)
        setHuffmanValue(node->right, node->huffmanValue + "1");
    else
        cout << node->value << ":" << node->huffmanValue << endl;
}

压缩

压缩文件分五步:
1. 读取源文件并统计所有字符出现的次数
2. 将这些字符生成哈弗曼节点集合
3. 将哈弗曼集合生成哈弗曼树
4. 对哈弗曼树的每个节点生成哈夫曼编码
5. 重新读取源文件,用每个字符对应的哈弗曼编码替代该字符生成压缩文件
根据以上五步,实现压缩的代码为:

void huffmanPack(string path){
    ifstream inFile(path, ios::binary);
    map<char, int> wordMap;
    char buff;
    int length = 0;
    while (!inFile.eof())//统计每个字符出现的次数
    {
        inFile.read(&buff, sizeof(buff));
        if (wordMap.find(buff) == wordMap.end())
            wordMap[buff] = 1;
        else
            wordMap[buff]++;
        length++;
    }
    vector<Node*> wordVec;
    map<char, Node*> huffmanWord;
    auto mapBegin = wordMap.begin();
    auto mapEnd = wordMap.end();
    while (mapBegin != mapEnd)//生成哈弗曼节点集合以及根据字符和节点的字典
    {
        Node* nodeTemp = new Node;
        nodeTemp->sortValue = mapBegin->second;
        nodeTemp->value = mapBegin->first;
        nodeTemp->huffmanValue = "";
        nodeTemp->left = nullptr;
        nodeTemp->right = nullptr;
        wordVec.push_back(nodeTemp);
        huffmanWord[nodeTemp->value] = nodeTemp;
        mapBegin++;
    }
    ofstream outFile("pack" + path, ios::binary);
    short fileSize = wordVec.size();
    outFile.write((char*)&fileSize, sizeof(short));//向压缩文件写入字符种类的数量
    sort(wordVec.begin(), wordVec.end(), SortByValue());
    int temp = 0;
    for (int i = 0; i<wordVec.size(); i++){//以字符出现数量的升序向压缩文件写入每个字符以及字符出现的次数
        buff = wordVec[i]->value;
        temp = wordVec[i]->sortValue;
        outFile.write(&buff, sizeof(buff));
        outFile.write((char*)&temp, sizeof(int));
    }
    int wordTag = wordVec.size()-1;
    while (wordTag-->0)//通过每次挑选权值最小的两个节点来生成哈夫曼树
    {
        sort(wordVec.begin(), wordVec.end(), SortByValue());
        Node* nodeTemp = new Node;
        nodeTemp->left = wordVec[0];
        nodeTemp->right = wordVec[1];
        nodeTemp->sortValue = wordVec[0]->sortValue + wordVec[1]->sortValue;
        nodeTemp->huffmanValue = "";
        wordVec[0] = nodeTemp;
        wordVec[1]->sortValue = 99999;
        cout << wordTag << endl;
    }
    if (wordVec.size()>1)
    setHuffmanValue(wordVec[0], "");
    else//防止文件中只有一种字符的情况
        wordVec[0]->huffmanValue = "1";

    inFile.close();
    ifstream inFilef(path, ios::binary);//再次打开该文件
    inFile.seekg(0, ios::beg);

    streampos   pos = inFilef.tellg(); 
    inFilef.seekg(0, ios::end);
    long long fileLength = inFilef.tellg();
    cout << "file length =" << fileLength << endl;
    inFilef.seekg(pos); 

    outFile.write((char*)&fileLength, sizeof(fileLength));//写入源文件的总长度

    char charTemp=0;
    length = 8;
    while (!inFilef.eof())//将源文件的每个字符的对应哈弗曼码写入压缩文件
    {
        inFilef.read(&buff, sizeof(buff));
        string str = huffmanWord[buff]->huffmanValue;
        for (int i = 0; i < str.size(); i++){       
            length--;
            //cout << str[i] << endl;
            if (str[i] == '0'){
                charTemp &= ~(1 << length);
            }               
            else{
                charTemp |= (1 << length);
            }               
            if (length == 0){//缓存字符八位写满后将其写入压缩文件并重置状态
                outFile.write(&charTemp, sizeof(charTemp));
                length = 8;
                charTemp = 0;
                fileLength--;
            }
        }
    }
    if (length != 0){
        outFile.write(&charTemp, sizeof(charTemp));
    }   
    inFilef.close();
    outFile.close();
}

解压缩

解压缩相当于压缩过程的逆过程,需要四步实现解压缩:
1. 读取压缩文件中的所有字符及其在源文件中的权值
2. 使用这些字符和权值生成哈弗曼节点集合
3. 将这些集合组成哈夫曼树
4. 将压缩文件中的哈弗曼编码用源文件中对应字符代替生成解压缩文件
根据以上四步,可以实现解压缩方法如下:

void huffmanRepack(string path){
    ifstream inFile(path, ios::binary);
    short count=0;
    inFile.read((char*)&count, sizeof(short));//获取源文件字符种类数量
    vector<Node*> wordVec;
    char buff;
    int temp;
    for (int i = 0; i<count; i++){//读取源文件每个字符及其权值并生成哈弗曼节点集合
        inFile.read(&buff, sizeof(char));
        inFile.read((char*)&temp, sizeof(int));
        Node* tempNode = new Node;
        tempNode->value = buff;
        tempNode->sortValue = temp;
        tempNode->huffmanValue = "";
        tempNode->left = nullptr;
        tempNode->right = nullptr;
        wordVec.push_back(tempNode);
    }

    int wordTag = wordVec.size() - 1;
    while (wordTag-->0){//重建哈夫曼树
        sort(wordVec.begin(), wordVec.end(), SortByValue());
        Node* nodeTemp = new Node;
        nodeTemp->left = wordVec[0];
        nodeTemp->right = wordVec[1];
        nodeTemp->sortValue = wordVec[0]->sortValue + wordVec[1]->sortValue;
        nodeTemp->huffmanValue = "";
        wordVec[0] = nodeTemp;
        wordVec[1]->sortValue = 99999;
        cout << wordTag << endl;
    }
    Node* huffmanTree = wordVec[0];
    //setHuffmanValue(wordVec[0], "");

    ofstream outFile("repack" + path, ios::binary);
    Node* huffmanTemp = huffmanTree;
    char tempBuff=0;
    long long fileLength = 0;
    inFile.read((char*)&fileLength, sizeof(fileLength));//读取源文件的长度
    while ((!inFile.eof()) && (outFile.tellp()<fileLength)){//将压缩文件中的哈弗曼编码转换为源文件对应的字符存入文件中
        inFile.read(&buff, sizeof(buff));
        for (int i = sizeof(buff)*8-1; i >= 0; i--){
            if (huffmanTemp->left != nullptr){
                if ((buff >> i) & 0x01){//对应二进制位为1
                    //cout << "1" << endl;
                    huffmanTemp = huffmanTemp->right;
                }
                else{
                    //cout << "0" << endl;
                    huffmanTemp = huffmanTemp->left;
                }
            }                   
            if (huffmanTemp->left == nullptr){//找到叶子节点后将该节点的字符写入文件并将根节点置位当前节点
                tempBuff = huffmanTemp->value;
                outFile.write(&tempBuff, sizeof(tempBuff));
                huffmanTemp = huffmanTree;
                if (outFile.tellp() >= fileLength)
                    break;
            }
            buff << i;
        }
    }
    inFile.close();
    outFile.close();
}

转载请注明出处:http://blog.csdn.net/ylbs110/article/details/51590414

你可能感兴趣的:(压缩,二进制,编码,解压缩,哈弗曼树)