【模板类、堆 实现Huffman树并对文件编码与解码】

Huffman树的简单介绍

  • Huffman树是根据元素的权重建立的,权重较小的离根结点较远,而权重较大的离根结点较近,从而使得整个Huffman树有着最小的带权路径长度。

  • 比如一篇文章,里面有不同的字符,不同的字符出现的频率是不尽相同的,Huffman树的元素就是这个字符,而它出现的频率就是他的权重。

下面是创建它的简单过程示意图:

项目CDEKLMUZ
频率324212074224372

先将他整理为升序

项目ZKMCUDLE
频率272432374242120

请添加图片描述重复上述步骤若干次后
请添加图片描述
这样就能得到一颗Huffman树了,接下来我们看看他的实现

利用基类定义叶子节点和非叶子节点

  • 因为叶子节点和非叶子节点包含的信息不同,所以我们实现 包括了一个抽象基类HuffNode 和两个子类 LeafNode、Intlnode。
#ifndef HUFFMAN
#define HUFFMAN
#include<bits/stdc++.h>
using namespace std;

template<class E>
class HuffNode {
public:
    virtual ~HuffNode(){}     //Base destructor
    virtual int weight() = 0;   //Return frequency
    virtual bool isLeaf() = 0;  //Determine type
    virtual HuffNode<E>* left() {}  //return leftchild
    virtual HuffNode<E>* right() {} //return rightchild
    virtual E val() {}      //return value
    virtual string getCode(){}  // return Huffcode
    virtual void setCode(string code){}  //set Huffcode
};


template<class E>
class LeafNode : public HuffNode<E> {
private:
    E it;
    int wgt;
    string code;
public:
    LeafNode( E val,int freq){
        it = val; wgt = freq;;
    }
    ~LeafNode(){}

    int weight() {return wgt;}

    E val () {return it;}

    void setCode(string code){
        this->code = code;
    }

    string getCode() {return code;}

    bool isLeaf() {return true;}  //is leaf
};


template<class E>
class IntlNode : public HuffNode<E> {
private:
    HuffNode<E>* lc;   //Left Child
    HuffNode<E>* rc;   //Right Child
    int wgt;
public:
    IntlNode(HuffNode<E>* l, HuffNode<E>* r){
        wgt = l->weight()+r->weight(); lc = l ; rc = r;
    }
    ~IntlNode(){};

    int weight() {return wgt;}

    bool isLeaf() {return false;}  //not leaf

    HuffNode<E>* left()  {return lc;}

    void setLeft(HuffNode<E>* b){
        lc = (HuffNode<E>*) b;
    }

    HuffNode<E>* right()  {return rc;}

    void setRight(HuffNode<E>* b){
        rc = (HuffNode<E>*) b;
    }
};

template<class E>
class HuffTree{
private:
    HuffNode<E>* Root;
public:
    HuffTree(E val, int freq){   //叶节点的构造
        Root = new LeafNode<E>(val,freq);
    }

    HuffTree(HuffTree<E>* l, HuffTree<E>* r){    //非叶节点的构造
        Root = new IntlNode<E>(l->root(), r->root());
    }
    ~HuffTree(){}
    HuffNode<E>* root() {return Root;}
};
#endif

堆的建立

  • 实现Huffman 节点的定义后,我们就要想 通过何种方式去实现它。
  • 通过原理图发现,都是取权重最小的两个元素 将它们连接之后,再按升序放进这个序列中,在重复此步骤 直到这个序列仅剩一个元素。
  • 由此我们联想到 利用小根堆来简化这些步骤
  • 这里就不介绍堆的相关知识,不懂的可以自己先了解一下
#ifndef HEAP_H
#define HEAP_H
using namespace std;

template<class E,class Comp>  //这里的Comp是实现大根堆小根堆比较规则的类
class heap {
private:
    E* Heap;    //用来存HuffTree节点
    int maxsize;
    int n;

    void siftdown(int pos){  //置为小根堆
        while(! isLeaf(pos)) {
            int j = leftchild(pos); 
            int rc = rightchild(pos);
            if ( (rc < n) && Comp::prior(Heap[rc],Heap[j]))  //找到儿子节点更小的位置
                j = rc;
            if (Comp::prior(Heap[pos], Heap[j])) return;  //如果父亲节点更小,直接返回
            swap(Heap, pos,j);  //否则调换位置
            pos =  j;
        }
    }
public:
    heap(E* h, int num, int max){
        Heap = h ; n = num ; maxsize = max ; buildHeap();
    }
    void buildHeap(){
        for (int i = n/2-1 ;i >=0 ; i-- ) siftdown(i);  
    }
    int isLeaf(int pos) const {
        return ((pos >= n/2) && (pos < n ));
    }

    int leftchild(int pos)const  {
        return 2*pos +1;
    }
    int rightchild(int pos) const {
        return 2*pos +2;
    }
    int parent(int pos) const {
        return (pos-1)/2;
    } 
    int size() const {
        return n;
    }
    E removefirst(){
        if( n > 0){
            swap(Heap,0,--n);  //要remove最小的,先把首尾交换位置,再把这个数siftdown合适的位置
            if ( n!=0 ) siftdown(0);
            return Heap[n];   //把最小的那个元素return 出来
        } 
    }

    void insert(const E it){
        if ( n < maxsize ){
            int curr = n++;
            Heap[curr] = it;   //先把这个元素插入到堆的尾部
            while (( curr != 0) && (Comp::prior(Heap[curr],Heap[parent(curr)]))){  
            		//判断是否满足调整的条件
                swap(Heap, curr , parent(curr));
                curr = parent(curr);
            }
        }  
    }
    void swap(E* Heap, int i, int j){ //交换两个元素
        E temp = Heap[i];
        Heap[i] = Heap[j];
        Heap[j] = temp;
    }
};

#endif
  • 下面是Comp的实现类
#ifndef COMP_H
#define COMP_H

#include "HuffNode.h"

class minTreeComp{
public:
    template<class E>
    static bool prior( HuffTree<E>* a, HuffTree<E>* b){
        return a->root()->weight() < b->root()->weight();  //小根堆
    }
};

#endif

Huffman树的创建

  • Huffman节点 和 堆 都定义好了之后, 我们就可以开始创建Huffman树了
template<class E>
HuffTree<E>* bulidHuff(HuffTree<E>* *TreeArray, int count ){
//传入的是节点数组和它的长度
    heap<HuffTree<E>*,minTreeComp>* forest = new heap<HuffTree<E>*,minTreeComp>(TreeArray,count,count);
//这里生成元素是Hufftree的小根堆
    HuffTree<E> *temp1, *temp2, *temp3 = NULL;
    while (forest->size() > 1) {
        temp1 = forest->removefirst();
        temp2 = forest->removefirst();  //取出两个最小的叶节点
        temp3 = new HuffTree<E>(temp1, temp2);  //合成一棵树
        forest->insert(temp3);  
        delete temp1;
        delete temp2;
    }
    return temp3;
};
  • 下面做一个简单的检验:用前序遍历遍历出这棵树的元素和权重
template<class E>
void preOrder(HuffNode<E>* root){
    if (root->isLeaf()){
        cout << root->val()<<"--"<< root->weight()<<"--"<<endl; 
        return;
    } else cout << root->weight()<<endl;
    preOrder(root->left());
    preOrder(root->right());
}
int main() {
    HuffTree<char>* * TreeArray = new HuffTree<char>* [8];

    TreeArray[0] = new HuffTree<char>('C',32); 
    TreeArray[1] = new HuffTree<char>('D',42);
    TreeArray[2] = new HuffTree<char>('E',120);
    TreeArray[3] = new HuffTree<char>('K',7);
    TreeArray[4] = new HuffTree<char>('L',42);
    TreeArray[5] = new HuffTree<char>('M',24);
    TreeArray[6] = new HuffTree<char>('U',37);
    TreeArray[7] = new HuffTree<char>('Z',2);

    HuffTree<char>* Root = bulidHuff<char>(TreeArray,8);
    HuffNode<char>* root = Root->root();
    preOrder(root);
}
  • 得到测试结果正确
306   
E--120
186   
79    
U--37 
D--42 
107   
L--42 
65    
C--32 
33    
9     
Z--2  
K--7  
M--24 

对叶子节点的编码

  • 只有叶子节点需要编码,从根往下遍历(如前序遍历),向左走编码+" 0 " ,向右走编码+” 1 “,直到到达叶子节点,为它setCode, 由此可以联想到递归
template<class E>
void Encode(string code, HuffNode<E>* root){
    if (root->isLeaf()){
        root->setCode(code);
        return;
    }
    Encode(code+"0",root->left());
    Encode(code+"1",root->right());
}

做一个简单的检验

template<class E>
void Encode(string code, HuffNode<E>* root){
    if (root->isLeaf()){
        root->setCode(code);
        return;
    }
    Encode(code+"0",root->left());
    Encode(code+"1",root->right());
}
int main() {
    HuffTree<char>* * TreeArray = new HuffTree<char>* [8];

    TreeArray[0] = new HuffTree<char>('C',32); 
    TreeArray[1] = new HuffTree<char>('D',42);
    TreeArray[2] = new HuffTree<char>('E',120);
    TreeArray[3] = new HuffTree<char>('K',7);
    TreeArray[4] = new HuffTree<char>('L',42);
    TreeArray[5] = new HuffTree<char>('M',24);
    TreeArray[6] = new HuffTree<char>('U',37);
    TreeArray[7] = new HuffTree<char>('Z',2);

    HuffTree<char>* Root = bulidHuff<char>(TreeArray,8);
    HuffNode<char>* root = Root->root();
    Encode("",root);
    preOrder(root);
}
  • 得到结果正确
306
E--120--0   
186
79
U--37--100  
D--42--101  
107
L--42--110  
65
C--32--1110 
33
9
Z--2--111100
K--7--111101
M--24--11111

对文件的编码与解码

  • 要对文件编码与解码,首先就要从文件中读取内容(文件的读写不做介绍),然后获取其中的字符种类与出现频率,由此我们容易联想到使用map来存放相应内容,让后通过map构建Huffman树,并对其编码。

这是text.txt文件里的内容

Stray birds of summer come to my window to sing and fly away.
And yellow leaves of autumn, which have no songs, flutter and fall there with
a sign. 
int main(){
    map<char,int> codeMap; //存放文件里的字符和频率
 
    ifstream in;
    ofstream out;
    in.open("text.txt");
    while (!in.eof()){
        char c ;
        in.get(c);
        codeMap[c]++;   //统计文件里的字符以及频率
    }
    in.close();

    int n = codeMap.size();
    HuffTree<char>* * TreeArray = new HuffTree<char>* [n];
    
    map<char,int>::iterator it = codeMap.begin();

    for (int i = 0; it != codeMap.end(); it++,i++){
        TreeArray[i] = new HuffTree<char>(it->first,it->second);   
    }
    HuffTree<char>* Root = bulidHuff<char>(TreeArray,n);    //创建Huffman树
    HuffNode<char>* root = Root->root();
    Encode("",root);  //为每个字符编码
    preOrder(root);
}

得到结果

149
62
 --28--00   
34
16
8
4
c--2--010000

--2--010001
4
v--2--010010
,--2--010011
t--8--0101
18
n--9--0110
o--9--0111
87
39
19
e--9--1000
10
d--5--10010
5
2
b--1--1001100
A--1--1001101
3
S--1--1001110
.--2--1001111
20
10
y--5--10100
r--5--10101
10
m--5--10110
f--5--10111
48
21
a--10--1100
11
h--5--11010
w--6--11011
27
13
i--6--11100
l--7--11101
14
7
g--3--111100
u--4--111101
s--7--11111
  • 要检验我们的编码是否正确,我们将文章内容编码后输出到另一个文件中,然后再利用这个文章的编码解码出对应的内容,看两者是否相同
  • 要实现该功能,我们首先需要一个能输出指定字符编码的函数,一个能对编码解码的函数
  • 对于对指定字符编码的函数,我们依然可以利用前序的思想找到对应字符的编码
  • 对于解码的函数,我们可以由上往下找,判断当前编码是 0 还是 1, 0 则往左走,1 则往右走,直到遇到叶子节点,然后再次从根出发·····
template <class E>
void findCharCode(string &code,char c, HuffNode<E>* root){
    if (root->isLeaf()){
        if (root->val() == c) code = root->getCode();
        return;
    }
    findCharCode(code,c,root->left());
    findCharCode(code,c,root->right());
}
    in.open("text.txt");
    out.open("textout.txt");
    while (!in.eof()){
        string code = "";
        char c;
        in.get(c);
        findCharCode(code,c,root);   //找到对应字符的编码
        // cout << c <<"--" << code <<endl;
        out << code;    //将编码放进编码文件
    }
    in.close();
    out.close();
//textout.txt文件内容
10011100101101011100101000010011001110010101100101111100011110111001111111110110110101101000101010001000001111011010000001010111001011010100001101111100011010010011111011000101011100111111110001101111000011000110100100010111111011010000110011011110010100100111101000110011010110100100010100100011101111010111110110011101100011000100101000111110001111011100110011110101011111011011001100100110011011110101110001000011010001101011000100101000000110011100111110111011011110011111010011001011111101111101010101011000101010011000110100100010111110011101111010001011101010001010110000011011111000101110100100011100001111111100111100011010011110000

然后解码

template<class E>
string decode(string code ,HuffNode<E>* root){
    string text = "";
    int len = code.length();
    HuffNode<E>* curr = root;
    int i= 0 ;

    while (i < len){
        if (code[i]=='0') curr = curr->left();
        else curr = curr->right();
        if (curr->isLeaf()){
            text += curr->val();
            curr = root;
        }
        i++;
    }
    return text;
}
    in.open("textout.txt");
    string code = "";
    while (!in.eof()){
        char str;
        in.get(str);
        code+=str;    //得到编码
    }
    in.close();
    string text = decode(code,root);  //对编码解码
    cout <<text;   //输入看是否与原文件相同
  • 得到输出结果与原内容一致
Stray birds of summer come to my window to sing and fly away.
And yellow leaves of autumn, which have no songs, flutter and fall there with
a sign.

结语

谨以此文记录自己的学习历程,如有错误,还望斧正
QWQ

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值