Huffman树的简单介绍
-
Huffman树是根据元素的权重建立的,权重较小的离根结点较远,而权重较大的离根结点较近,从而使得整个Huffman树有着最小的带权路径长度。
-
比如一篇文章,里面有不同的字符,不同的字符出现的频率是不尽相同的,Huffman树的元素就是这个字符,而它出现的频率就是他的权重。
下面是创建它的简单过程示意图:
项目 | C | D | E | K | L | M | U | Z |
---|---|---|---|---|---|---|---|---|
频率 | 32 | 42 | 120 | 7 | 42 | 24 | 37 | 2 |
先将他整理为升序
项目 | Z | K | M | C | U | D | L | E |
---|---|---|---|---|---|---|---|---|
频率 | 2 | 7 | 24 | 32 | 37 | 42 | 42 | 120 |
重复上述步骤若干次后
这样就能得到一颗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