• 哈夫曼之谜


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

    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] != '')
        	{	
        		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;
        }
        
    
    
  • 相关阅读:
    20181020遭遇战
    二分图最大分配
    2019.11.11 洛谷月赛t3
    2019.10.29 CSP%您赛第四场t2
    2019.10.28 CSP%您赛第四场t3
    2019.10.26 CSP%您赛第三场
    2019.10.24 CSP%你赛第二场d1t3
    0080-简单的排序
    0079-简单的循环
    0078-求最大因子
  • 原文地址:https://www.cnblogs.com/vachester/p/6690098.html
Copyright © 2020-2023  润新知