哈夫曼之谜

  这篇博客里我先是介绍了什么是哈夫曼树,然后给出了如何去构造其的算法,接着引进哈夫曼编码,最后拓展到了动态哈夫曼树。废话不多说,开始吧!

1. 什么是哈夫曼

要介绍哈夫曼树,得先了解几个概念:

  • 扩充二叉树:在原来的二叉树(内结点)中出现空子树时,加上特殊的结点(外结点),使得原结点在新树上不存在叶结点,假设有n个内结点和S个外结点,E表示从根结点到每个外结点的长度之和,I表示从根结点到每个内结点长度之和,长度即是经过的边的数量,性质如下:
    • 性质1:S = n + 1
    • 性质2:E = I + 2*n

image_1bdb79vmg1mel1song1s12om7se9.png-23.2kB

  • 加权路长:在扩充二叉树的基础上,若每个外结点都对应一个值,则假设lj是从根结点到某个外结点的路长,而wj是该外结点对应的值,则∑wj*lj便是加权路长。

  • 哈夫曼树:对于给定的实数w1,w2,...,wm,构造一棵扩充二叉树,使其的加权路长最小,这棵树就是哈夫曼树。

2. 哈夫曼树的构造算法:

  • 由给定的n个权值{w0,w1,...,wn-1},构造具有n棵扩充二叉树的森林F={T0,T1,...,Tn-1},其中每棵扩充二叉树Ti只有一个带权值wi的根结点,其左右子树均为空
  • 在F中选取两棵根结点的权值最小的扩充二叉树,作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值是它们的和
  • 在F中删除这两棵树,加入新树
  • 重复2,3步,直到只剩一棵树

image_1bdbdmmtp100e56ll61dl91ifem.png-58.5kB

3. 哈夫曼编码

  讲道理,假设给你一篇文档,里面全是a,b,c,d,e,你统计出来它们的出现频率,如下:

字符 | 概论

  • | -
    a | 0.12
    b | 0.4
    c | 0.15
    d | 0.08
    e | 0.25

  字母是以ASCII码存储的,一个字符8位,在知道概率的情况下,这种存储方式很浪费硬盘空间,设计一个编码方式,让它们在最小空间内存储,且具有前缀性(任何一个字符的编码不会是别的字符的编码前缀),这时候哈夫曼编码应运而生。

只需要将字符概率看成权值即可,然后构造哈夫曼树,在左子树的边上标记'0',右标记'1',则每个字符的路径上标记相连即编码。

image_1bdbeur5qe1r124e143r8mptnu13.png-33.1kB

  简单代码实现:

    //假设C是一个n个字符的集合,其中每个字符c属于C且c.freq给出它们的概率,Q是以freq比较的最小优先队列
    
    HUFFMAN(C)
    {
        n = |C|;
        Q = C;
        for i = 1 to n-1
            allocate a new node z
            z.left = x = EXTRACT_MIN(Q);
            z.right = y = EXTRACT_MIN(Q);
            z.freq = x.freq + y.freq;
            INSERT(Q,z);
    }
    

4. 动态哈夫曼

  静态哈夫曼编码最大的缺点是它需要对原始数据进行两遍扫描:第一遍统计字符频率,第二遍根据频率得到的哈夫曼树对原始数据进行编码。而动态哈夫曼树对t+1个字符的编码是根据前面t个字符而来,不需要重新进行扫描。

  • 一些定义

    • 原始数据:需要被压缩的数据
    • 压缩数据:被压缩过的数据
    • n:字母表的长度
    • aj:字母表的第j个字符
    • t:已经处理的原始数据中字符的总个数
    • k:已经处理数据中各不相同字符的个数
  • 构造规则:

    • 初始化,一个根节点和左分支,权值均为空
    • 对每个结点都分配了一个序列号,序列号越大,该结点权值越大
    • 每读进一个字符,若字符未出现,则在空叶结点右分支加入权值为1新结点,左分支加入权值为0的叶子结点,调整后,更新各结点权值;若已经出现过,调整后更新权值
    • 调整方法:先以对应的叶结点为当前结点,重复地与具有相同权值的序号最大的结点进行交换,并且使后者的父亲结点作为新的当前结点,直到遇到根结点为止

  例如,已经压缩完32个字符

image_1bdbjj97toccdc4ccdbdtomn1g.png-22.7kB

  接着压缩一个'b',先要调整:

image_1bdbjkd401dq311rovj8b33hqh1t.png-23.2kB

  接着更新结点:

image_1bdbjlbse1154dna1e8ajb717pa2a.png-24.1kB

  • 不多说,直接放代码:
    #include <iostream>
    #include <fstream>
    #include <vector>
    using namespace std;
    
    #define MAX_ORDER 512
    
    struct Node
    {
    	char data;      
    	int weight;    
    	int order;   
    	
    	Node* lchild;
    	Node* rchild;
    	Node* parent;
    };
    
    Node * root = new Node; // 定义哈夫曼树的根节点
    
    vector<Node *> Weight; //存储同权重中最大序号的节点
    vector<Node *> Leaf;  //存储所有叶子节点
    
    //初始化root和weight
    void init()
    {
    	root->order = 512;
    	root->weight = 0;
    	root->data = '0';
    	root->lchild = NULL;
    	root->rchild = NULL;
    	root->parent = NULL;
    
    }
    
    //参数化初始化nyt节点
    void init_nyt(Node * nyt)
    {
    	nyt->weight = 0;
    	nyt->order = 0;
    	nyt->data = '0';
    	nyt->lchild = NULL;
    	nyt->rchild = NULL;
    	nyt->parent = NULL;
    }
    
    //根到叶节点路径上所有节点权重加一
    void increase(Node * leaf)
    {
    	Node * tmp = leaf;
    	while(tmp != root)
    	{
    		tmp->weight += 1;
    		tmp = tmp->parent;
    	}
    	root->weight += 1;
    }
    
    
    //更新Weight
    void change_weight(Node * present)
    {
    	Node * tmp = present;
    	bool flag;
    	if(!Weight.size())
    		Weight.push_back(present);
    	else
    	{
    		while(tmp != root)
    		{
    			flag = false;
    			for(int num=static_cast<int>(Weight.size()),i=0;i < num;i++)
    			{
    				if(Weight[i]->order == tmp->order)
    					flag = true;
    			}
    			if(!flag)
    				Weight.push_back(tmp);
    			tmp = tmp->parent;
    		}
    	}
    }
    
    //返回同权重最大节点
    Node * Max(int wei)
    {
    	Node * Max;
    	int i = 0;
    	int num= static_cast<int>(Weight.size());
    	for(;i < num;i++)
    	{
    		if(Weight[i]->weight == wei)
    		{
    			Max = Weight[i];
    			break;
    		}
    	}
    	for(;i < num;i++)
    	{
    		if(Weight[i]->weight == wei && Weight[i]->order > Max->order)
    			Max = Weight[i];
    	}
    	return Max;
    }
    
    
    //交换节点以便维持哈夫曼树性质
    void exchange(Node * present)
    {
    	Node * tmp = present;
    	Node * max;
    	Node * p = NULL;
    	Node * p1, *p2;
    	int t;
    	if(present == root || present->weight == 0)
    		return;
    	else
    	{
    		while(tmp && tmp != root)
    		{
    			max = Max(tmp->weight);
    			if(max != tmp->parent && max->order > tmp->order)
    			{
    				p1 = max->parent;
    				p2 = tmp->parent;
    				p = p1;
    				t = tmp->order;
    				tmp->order = max->order;
    				max->order = t;				
    				tmp->parent = p1;
    				max->parent = p2;
    				if(p1->lchild == max)
    					p1->lchild = tmp;
    				else
    					p1->rchild = tmp;
    				if(p2->lchild == tmp)
    					p2->lchild = max;
    				else
    					p2->rchild = max;
    			}
    			else
    				p = tmp->parent;
    			tmp = p;
    		}
    	}
    }
    
    //编码
    void encode(ofstream & out)
    {
    	Node * tmp;
    	Node * par;
    	vector<int> code;
    	int num1 = static_cast<int>(Leaf.size());
    	int num2;
    	for(int i=0;i<num1;i++)
    	{
    		code.clear();
    		out << Leaf[i]->data << ": ";
    		tmp = Leaf[i];
    		while(tmp != root)
    		{
    			par = tmp->parent;
    			if(par->lchild == tmp)
    				code.push_back(0);
    			else
    				code.push_back(1);
    			tmp = par;
    		}
    		num2 = static_cast<int>(code.size());
    		for(int j=0;j<num2;j++)
    			out << code[num2-1-j] << " ";
    		out << endl;
    	}
    }
    
    //添加信息,构建动态哈夫曼树
    void Add(ifstream & in,ofstream & out)
    {
    	string str;
    	in >> str;
    	Node * p; 
    	Node * q; //记录当前的NYT节点
    	Node * nyt; //即NYT节点
    	Node * present; //记录当前节点
    	int i = 0;
    	bool flag;
    	while(str[i] != '\0')
    	{	
    		flag = false;
    		int j = 0;
    		for(int num=static_cast<int>(Leaf.size());j < num;j++)
    		{
    			if(Leaf[j]->data == str[i])
    			{
    				flag = true;
    				break;
    			}
    		}
    		if(!flag)
    		{
    			if(!root->weight)
    				q = root;
    			p = new Node;
    			nyt = new Node;
    			init_nyt(nyt);
    			p->parent = nyt->parent = q;
    			p->weight = 0;
    			p->data = str[i];
    			p->order = q->order -1;
    			nyt->order = q->order -2;
    			q->lchild = nyt;
    			q->rchild = p; 
    			present = p;
    			Leaf.push_back(p);
    			q = nyt;
    		}
    		else
    			present = Leaf[j];
    		exchange(present);
    		increase(present);
    		change_weight(present);
    		encode(out);
    		out << endl;
    		i ++;
    	}
    }
    
    
    
    int main()
    {
    	ifstream in("data.in");
    	ofstream out("data.out");
    	init();
    	Add(in,out);	
    	in.close();
    	out.close();
    	return 0;
    }
    

posted @ 2017-04-10 17:40  va_chester  阅读(838)  评论(0编辑  收藏  举报