二叉树

Posted shengzhe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树相关的知识,希望对你有一定的参考价值。

哈夫曼编码

哈夫曼编码其实就是根据每个字符出现的频率制定其对应的01编码

哈夫曼编码满足同一套编码中没有任何一串编码是另一串的前缀(否则有多种翻译方式)

例如这样一串字符串:accbac

我们可以得到如下的表格

字符 出现次数 哈夫曼编码
a 2 01
b 1 10
c 3 1

在这套哈夫曼编码中,没有任何一串编码为另一串编码的前缀

而且有一个重要的特点:出现频率越高,编码长度越小

很容易就能想出这是为了节省空间

但如何构造一套符合这个特点的哈夫曼编码呢?

构造哈夫曼树

首先,哈夫曼树并不完全是二叉树,哈夫曼树的叉根据编码的进制而定

我们在此研究的是二叉的哈夫曼树,哈夫曼编码也就是二进制下的字符串(01串)

那么如何构造一棵哈夫曼树呢?

我还是以accbac这个字符串举例

在这个字符串里,a出现了2次,b出现了1次,c出现了3次

我们先把所有的字符都变成一棵棵只有根结点的树

现在我们有了一个森林,森林里共三棵树:2,1,3

我们为了让出现频率高的字符对应的编码尽量短,最后构造出来的树中肯定出现频率高的字符层级靠上,出现频率低的字符层级靠下

所以我们从下往上构造树

接下来的算法很像合并果子

首先找出根节点最小的两棵2,1

将它们分别作为新树的左孩子与右孩子

新树的根是他们之和,也就是3

现在我们还剩两棵树,一棵是根节点为3,左孩子为1,右孩子为2的树,另一棵是只有根节点(3)的树

我们再选出根节点最小的两棵树合并

于是我们就得到了这样一棵哈夫曼树

技术图片

而它们对应的哈夫曼编码又是什么呢?

我们从根节点开始向下走

往左子树走一步的路径权值为1,往右子树走一步的路径权值为0

技术图片

于是这样的一套哈夫曼编码就构造完啦

那如何论证正确性呢?(论证为何符合没有任一编码为另一编码的前缀)

其实这很简单

因为每个字符在最终的哈夫曼树中都是叶子节点

所以它们不可能有左右孩子

而且从根节点到每个叶子节点的路径都是唯一的

所以不存在某条从根节点到一个叶子节点的路径在另一条从根节点到另一个叶子节点的路径上

而哈夫曼树左叉为1,右叉为0,不可能出现两条不一样的路径,编码却一样的情况

那也就不会有一个字符串是另一个的前缀了


蒟蒻从《算法竞赛宝典》搬来了一段代码(懒得写)

//哈夫曼编码 
#include <iostream>
using namespace std;
#define MAXN 1000
#define MAXLEAF 30
#define MAXNODE MAXLEAF*2 -1
typedef struct //编码结构体
{
  int bit[100];//保存哈夫曼编码 
  int start;//编码的开始位置 
}HCode;   
typedef struct//结点结构体
{
  int weight;
  int parent;
  int lchild;
  int rchild;
}HNode;        
HNode HuffNode[MAXNODE];//定义一个结点结构体数组
HCode HuffCode[MAXLEAF];//定义一个编码结构体数组
void CreatHuffmanTree(HNode HNode[MAXNODE],int n)//构造一颗哈夫曼树
{ 
    int i,j;
    int m1,m2;//构造树过程中两个最小权值结点的权值
    int x1,x2;//构造树过程中两个最小权值结点在数组中的序号
    for(i=0;i<n-1;i++)//循环构造 Huffmantree
    {
        m1=m2=10000;//m1、m2中存放两个无父结点且结点权值最小的两个结点
        x1=x2=0;
        for(j=0;j<n+i;j++)//找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树
        {
            if(HNode[j].weight<m1&&HNode[j].parent==-1)
            {
                m2=m1; 
                x2=x1; 
                m1=HNode[j].weight;
                x1=j;
            }
            else if(HNode[j].weight<m2&&HNode[j].parent==-1)
            {
                m2=HNode[j].weight;
                x2=j;
            }
        }
        //设置找到的两个子结点 x1、x2 的父结点信息
        HNode[x1].parent=n+i;
        HNode[x2].parent=n+i;
        HNode[n+i].weight=HNode[x1].weight + HNode[x2].weight;
        HNode[n+i].lchild=x1;
        HNode[n+i].rchild=x2;
    }
}
void CreatHuffmanCode(HNode HuffNode[MAXNODE],int n)
{
    HCode cd;//定义一个临时变量来存放求解编码时的信息   
    for(int i=0;i<n;i++)
    {
        cd.start=n-1;
        int c=i;
        int p=HuffNode[c].parent;
        while(p!=-1)   // 父结点存在 
        {
            if(HuffNode[p].lchild==c)
                cd.bit[cd.start]=0;
            else
                cd.bit[cd.start]=1;
            cd.start--;        // 求编码的低一位 
            c=p;                    
            p=HuffNode[c].parent;    // 设置下一循环条件 
        } 
        for(int j=cd.start+1;j<n;j++)//保存求出的每个叶结点的哈夫曼编码和编码的起始位
        { 
            HuffCode[i].bit[j]=cd.bit[j];
        }
        HuffCode[i].start=cd.start;
    }                  
} 
void init(HNode HNode[],int n)//初始化
{
    for(int i=0;i<2*n-1;i++) 
    {
        HNode[i].weight= 0;
        HNode[i].parent=-1;
        HNode[i].lchild=-1;
        HNode[i].lchild=-1;
    }
    for(int i=0;i<n;i++)
    {
        cout<<"请输入第"<<i<<"个结点的权重";  
        cin>>HNode[i].weight;
    }    
}
int main()
{
    int i,j,n;
    cout<<"请输入字符个数n:
";
    cin>>n;
    init(HuffNode,n);//初始化 
    CreatHuffmanTree(HuffNode, n);//建立哈夫曼树 
    CreatHuffmanCode(HuffNode, n);//生成哈夫曼编码  
    for(i=0;i<n;i++)//输出哈夫曼编码
    {
        cout<<i<<"的哈夫曼编码为:";
        for(j=HuffCode[i].start+1;j<n;j++)
          cout<<HuffCode[i].bit[j];
        cout<<endl;
    }
    return 0;
}

 

以上是关于二叉树的主要内容,如果未能解决你的问题,请参考以下文章

数据结构 二叉树

数据结构二叉树经典基础习题

数据结构 二叉树的简单理解和代码实现

用c语言写二叉树,源代码。

数据结构中二叉树的顺序存储结构代码怎么编写?

二叉树练习题