理论基础 —— 二叉树 —— 哈夫曼树与哈夫曼编码

【哈夫曼树】

1.相关概念

1)叶结点的权值:对叶结点赋予的一个有意义的数值量

2)二叉树的带权路径长度(WPL):设二叉树具有 n 个带权叶结点,从根结点到各叶结点的路径长度与相应叶节点权值的乘积之和

即:WPL=\sum_{k=1}^n w_k l_k,其中 Wk 为第 k 个叶结点的权值,Lk 为从根结点到第 k 个叶结点的路径长度

如图所示,WPL=2*2+3*2+4*2+7*2=4+6+8+14=32

3)哈夫曼树:给定一组具有确定权值的叶结点,可以构造出不同的二叉树,其中 WPL 最小的二叉树称为哈夫曼树。

2.哈夫曼树特点

1)权值越小,离根越远;权值越大,离根越近。

2)只有度为 0 的叶结点和度为 2 的分支结点,不存在度为 1 的结点

3.哈夫曼算法

1)初始化:由给定的 n 个权值构造 n 棵只有一个根节点的二叉树,得到一个二叉树集合 F

2)选取与合并:从二叉树集合 F 中选取根节点权值最小的两棵二叉树分别作为左右子树构造一棵新的二叉树,这棵新二叉树的根节点的权值为其左、右子树根结点的权值和

3)删除与加入:从 F 中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到 F 中

4)重复 2)、3) 步,当集合中只剩下一棵二叉树时,这棵二叉树就是哈夫曼树

4.计算WPL

给出 n 个叶结点的权值,计算构成哈夫曼树的 WPL

int main() {
    int n;
    cin>>n;
    priority_queue<int,vector<int>,greater<int> > huffman;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        huffman.push(x);
    }
    int res=0;
    for(int i=1;i<n;i++){
        int x=huffman.top();huffman.pop();
        int y=huffman.top();huffman.pop();
        int temp=x+y;
        res+=temp;
        huffman.push(temp);
    }
    cout<<res<<endl;
    return 0;
}

【哈夫曼编码】

1.编码

在进行程序设计时,通常给每一个字符标记一个单独的代码来表示一组字符,即编码。

在进行二进制编码时,假设所有的代码都等长,那么表示 n 个不同的字符需要 \left \lceil log_2\:n \right \rceil 位,这称为等长编码。

如果每个字符的使用频率相等,那么等长编码无疑是空间效率最高的编码方法,而如果字符出现的频率不同,则可以让频率高的字符采用尽可能短的编码,频率低的字符采用尽可能长的编码,来构造出一种不等长编码,从而获得更好的空间效率。

在设计不等长编码时,要考虑解码的唯一性,如果一组编码中任一编码都不是其他任何一个编码的前缀,那么称这组编码为前缀编码,其保证了编码被解码时的唯一性。

2.哈夫曼编码

哈夫曼树可用于构造最短的前缀编码方案,即哈夫曼编码,具体做法如下:

1)设需要编码的字符集为:\{d_1,d_2,...,d_n\},他们在字符串中出现的频率为:\{w_1,w_2,...,w_n\}

2)以 d_1,d_2,...,d_n 作为叶结点,w_1,w_2,...,w_n 作为叶结点的权值,构造一棵哈夫曼树

3)规定哈夫曼编码树的左分支代表 0,右分支代表 1,则从根结点到每个叶结点所经过的路径组成的 0、1 序列即为该叶结点对应字符的编码

3.实现

struct Node{
    int weight;
    Node *lchild,*rchild;
};
typedef Node *tree;
tree create(int arr[],int n){
    tree forest[N];
    tree root=NULL;
    for (int i=0; i<n; i++){//将所有点存入森林
        tree temp;
        temp=(tree)malloc(sizeof(Node));
        temp->weight = arr[i];
        temp->lchild = temp->rchild = NULL;
        forest[i]=temp;
    }

    for(int i=1; i<n; i++) {//n-1次循环建哈夫曼树
        int minn=-1,minnSub;//minn为最小值树根下标,minnsub为次小值树根下标
        for(int j=0; j<n; j++) {
            if (forest[j]!=NULL && minn==-1) {
                minn=j;
                continue;
            }
            if (forest[j]!=NULL) {
                minnSub=j;
                break;
            }
        }

        for (int j=minnSub; j<n; j++){//根据minn与minnSub赋值
            if(forest[j]!=NULL){
                if(forest[j]->weight < forest[minn]->weight){
                    minnSub=minn;
                    minn=j;
                }
                else if(forest[j]->weight < forest[minnSub]->weight){
                    minnSub=j;
                }
            }
        }

        //建新树
        root = (tree)malloc(sizeof(Node));
        root->weight = forest[minn]->weight + forest[minnSub]->weight;
        root->lchild = forest[minn];
        root->rchild = forest[minnSub];

        forest[minn]=root;//指向新树的指针赋给minn位置
        forest[minnSub]=NULL;//minnSub位置为空
    }
    return root;
}
int getWPL(tree &root,int len){//计算哈夫曼树带权路径长度WPL
    if(root==NULL)
        return 0;
    else {
        if(root->lchild==NULL&&root->rchild==NULL)//叶节点
            return root->weight*len;
        else
            return getWPL(root->lchild,len+1)+getWPL(root->rchild,len+1);
    }
}
void HuffmanCoding(tree &root,int len,int arr[]){//哈夫曼树编码
    if(root!=NULL){
        if(root->lchild==NULL&&root->rchild==NULL){
           printf("结点为%d的字符的编码为: ",root->weight);
           for(int i=0;i<len;i++)
               printf("%d", arr[i]);
           printf("\n");
        }
        else{
            arr[len]=0;
            HuffmanCoding(root->lchild,len+1,arr);
            arr[len]=1;
            HuffmanCoding(root->rchild,len+1,arr);
        }
    }
}
int main(){
    int n=6;//长度
    int arr[]={3,9,5,12,6,15};//权值
    tree root=create(arr,n);

    int WPL=getWPL(root,0);
    printf("WPL=%d\n",WPL);

    printf("==========各节点的哈夫曼树编码==========\n");
    HuffmanCoding(root,0,arr);

    return 0;
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值