哈夫曼之谜

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

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,你统计出来它们的出现频率,如下:

字符概论
a0.12
b0.4
c0.15
d0.08
e0.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;
    }
    

转载于:https://www.cnblogs.com/vachester/p/6690098.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值