哈夫曼树(哈夫曼编码)

欢迎观看我的博客,如有问题交流,欢迎评论区留言,一定尽快回复!(大家可以去看我的专栏,是所有文章的目录)
文章字体风格:
红色文字表示:重难点
蓝色文字表示:思路以及想法

哈夫曼树(哈夫曼编码)

  • 1. 什么是哈夫曼树
  • 2. 哈夫曼树的创建
  • 3. 哈夫曼树的应用(哈夫曼编码)
  • 4. 哈夫曼编码的例题

1. 什么是哈夫曼树

若一个二叉树的带权路径长度达到最小,称这样的二叉树为最优二叉树。 (什么是带权路径长度呢?就是一个二叉树的叶子节点上的权值(权值不是叶子结点的所表示的数据,而就是一个值两点之间的路径值,) 乘以 该叶子结点所在的层数)
并且哈夫曼树中,只有叶子结点才是所代表的节点(其余节点,其实是在创建过程中,补充的节点,接下来的哈夫曼树的创建,就明白了。)

2. 哈夫曼树的创建

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,哈夫曼树的构造规则为:

  1. 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
  2. 在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
  3. 从森林中删除选取的两棵树,并将新树加入森林
  4. 重复(02)、(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

以{5,6,7,8,15}为例,来构造一棵哈夫曼树
哈夫曼树(哈夫曼编码)_第1张图片
由图可以看出,类似于11,15,26都是补充的,而不是原本的节点。所以,哈夫曼树中叶子结点才是最重要的

3. 哈夫曼树的应用(哈夫曼编码)

哈夫曼编码:
哈夫曼树可以直接应用于通信及数据传送中的二进制编码。设: D={d1,d2,d3…dn}为需要编码的字符集合。
W={w1,w2,w3,…wn}为D中各字符出现的频率。 现要对D中的字符进行二进制编码,使得:
(1) 按给出的编码传输文件时,通信编码的总长最短。
(2) 若di不等于dj,则di的编码不可能是dj的编码的开始部分(前缀)。
满足上述要求的二进制编码称为最优前缀编码。 上述要求的第一条是为了提高传输的速度,第二条是为了保证传输的信息在译码时无二性,所以在字符的编码中间不需要添加任意的分割符。
对于这个问题,可以利用哈夫曼树加以解决:用d1,d2,d3…dn作为外部结点,用w1,w2,w3…wn作为外部结点的权,构造哈夫曼树。在哈夫曼树中把从每个结点引向其左子结点的边标上二进制数“0”,把从每个结点引向右子节点的边标上二进制数“1”,从根到每个叶结点的路径上的二进制数连接起来,就是这个叶结点所代表字符的最优前缀编码。通常把这种编码称为哈夫曼编码。例如:
D={d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11,d12,d13} W={2,3,5,7,11,13,17,19,23,29,31,37,41} 利用哈夫曼算法构造出如下图所示的哈夫曼树:哈夫曼树(哈夫曼编码)_第2张图片

从而得到各字符的编码为:

d1:1011110 d2:1011111

d3:101110 d4:10110

d5:0100 d6:0101

d7:1010 d8:000

d9:001 d10:011

d11:100 d12:110

d13:111

编码的结果是,出现频率大的字符其编码较短,出现频率较小的字符其编码较长。

4. 哈夫曼编码的例题

原题链接
哈夫曼树(哈夫曼编码)_第3张图片
思路:

判断哈夫曼编码有两个条件:

  1. WPL最小(由于本题所给的数据是编码,不能求最短带径长度,我们可以通过每个节点出现的次数,进而求得所有节点出现的总次数而判断
    首先我们构建所给数据的哈夫曼树,然后把所有的节点权值相加起来,这就是这棵树所有节点出现的总次数。
    之后我们把需要判断的信息的 编码长度 × 出现的次数,的总和就是所有节点出现的次数。我们通过判断这两个值,就可以知道,是不是同一个哈夫曼树了)
    (原因如下:哈夫曼树(哈夫曼编码)_第4张图片
    由图中我们可以看出,编号0,出现一次,也就是节点出现一次,所以这样的话,我们就可以通过对所给信息,和需要判断的信息进行对比,进而比较是否为同一个哈夫曼树,或者说,是否满足wpl都是最小的性质
  2. 一个编码不能是其他编码的前缀
#include 
#include 
#include 
using namespace std;
int sum=0;
bool Check(int *a,string *ss,int n)
{
    int len=0;
    string l,s;
    int c;
    for(int i=0; i<n; i++)
    {
        for(int j=i+1; j<n; j++)
        {
            c=min(ss[i].length(),ss[j].length());
            if(ss[i].substr(0,c)==ss[j].substr(0,c))//检查是否存在一个编码是另一个编码的前缀
            {
                return false;
            }
        }
        len+=a[i]*ss[i].length();
    }
    if(len!=sum)//如果长度不是最短长度
        return false;
    return true;
}
int main()
{
    priority_queue<int,vector<int>,greater<int> > Q;
    string ss[1001],s,code[1001];
    int n,n2,a[1001],t=0;
    cin>>n;
    for(int i=0; i<n; i++)
    {
        cin>>ss[i];
        cin>>a[i];
        Q.push(a[i]);
    }
    while(Q.size()>1)//求最短编码的长度
    {
        t=0;
        t+=Q.top();
        Q.pop();
        t+=Q.top();
        Q.pop();
        sum+=t;
        Q.push(t);
    }
    cin>>n2;
    for(int i=0; i<n2; i++)
    {
        for(int j=0; j<n; j++)
        {
            cin>>s;
            cin>>code[j];
        }
        if(Check(a,code,n)==true)
        {
            cout<<"Yes"<<endl;
        }
        else
        {
            cout<<"No"<<endl;
        }
    }
    return 0;
}

你可能感兴趣的:(算法)