哈夫曼树与哈夫曼编码

哈弗曼树与哈弗曼编码

一、前置知识

什么是编码

‘a’ = 97 = ( 0110   0001 ) 2 (0110\ 0001)_2 (0110 0001)2

‘0’ = 48 = ( 0011   0000 ) 2 (0011\ 0000)_2 (0011 0000)2

注意:任何信息,在计算机中,都是二进制存储的

信息:“ a a 00 aa00 aa00” = 01100001 、 01100001 、 00110000 、 00110000 01100001、01100001、00110000、00110000 01100001011000010011000000110000

一台计算机 传输到 另外一台计算机,传输 32 个比特位

假设:计算机的网络是 32 b i t / s 32bit/s 32bit/s。所以用时: 1   s 1 \ s 1 s

特定场景:只有 a , b , 0 , 1 a,b,0,1 a,b,0,1 四种字符需要传输

自制My_X编码: a : 00 ,    b : 01 ,    0 : 10 ,    1 : 11 a:00, \ \ b: 01,\ \ 0: 10,\ \ 1: 11 a:00,  b:01,  0:10,  1:11

a a 00 aa00 aa00” = 00001010 00001010 00001010

在带宽不变的情况下,当前只需要传输 0.25   s 0.25\ s 0.25 s

定长与变长编码

  1. A S C I I ASCII ASCII 编码 和 特定场景下的My_X编码,都属于定长编码
  2. 对于每一个字符,编码长度相同,这就是定长编码
  3. 【大家自行补充】 U T F − 8 UTF-8 UTF8编码,是变长编码, U T F − 16 UTF-16 UTF16,是定长编码
  4. 对于每一个字符,编码长度不相同,这就是变长编码
  5. 将定长编码,看成是变长编码的特例
  6. 变长编码,一定不差于定长编码

  

变长编码应用场景

特定场景:

  1. 只有四种字符 : a 、 b 、 0 、 1 a、b、0、1 ab01

  2. 出现概率: a : 0.8 ,    b : 0.05 ,    0 : 0.1 ,    1 : 0.05 a: 0.8,\ \ b: 0.05, \ \ 0: 0.1,\ \ 1: 0.05 a:0.8,  b:0.05,  0:0.1,  1:0.05

  3. 平均编码长度: a v g ( l e n ) = ∑ l e n i × p i avg(len) = \sum{len_i}\times{p_i} avg(len)=leni×pi

    l e n i len_i leni:第 i 种字符,编码长度

    p i p_i pi:第 i 种字符,出现概率

  

My_X 编码的平均编码长度: a v g ( l ) = 2 × ∑ p i = 2 avg(l) = 2\times\sum{p_i}=2 avg(l)=2×pi=2

(My_X自己设计的定长编码)
平均编码长度:2,假设传输100个字符,则估算需传输200个比特位

新设计 My_Y 编码: a : 1 、    b : 01 、    0 : 000 、    1 : 001 a: 1、\ \ b: 01、\ \ 0: 000、\ \ 1: 001 a:1  b:01  0:000  1:001

平均编码长度: 1 ∗ 0.8 + 2 ∗ 0.05 + 3 ∗ 0.1 + 3 ∗ 0.05 = 1.35 1*0.8+2*0.05+3*0.1+3*0.05=1.35 10.8+20.05+30.1+30.05=1.35

(My_Y自己设计的变长编码)
平均编码长度:1.35,假设传输100个字符,则估算需传输135个比特位

  

二、哈弗曼编码

  1. 首先,统计得到每一种字符的概率
  2. n 个字符,建立成一棵哈弗曼树
  3. 每一个字符,都落在叶子结点上。且此字符的出现频率也可叫做结点的权
  4. 按照左0右1的形式,将编码读取出来

具体来说,依旧用只有四种字符 : a 、 b 、 0 、 1 a、b、0、1 ab01,且出现概率为 a : 0.8 ,    b : 0.05 ,    0 : 0.1 ,    1 : 0.05 a: 0.8,\ \ b: 0.05,\ \ 0: 0.1,\ \ 1: 0.05 a:0.8,  b:0.05,  0:0.1,  1:0.05来举例说明

  • n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;

  • 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;

  • 重复 12 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。

在这里插入图片描述

得到新编码:

a : 0 ∣ b : 110 ∣ 0 : 10 ∣ 1 : 111 a: 0 | b: 110 | 0: 10 | 1: 111 a:0b:1100:101:111

平均编码长度: 1 ∗ 0.8 + 3 ∗ 0.05 + 2 ∗ 0.1 + 3 ∗ 0.05 = 1.3 1*0.8+3*0.05+2*0.1+3*0.05=1.3 10.8+30.05+20.1+30.05=1.3

哈夫曼编码(变长)
平均编码长度:1.3,假设传输100个字符,则估算需传输130个比特位

  

结论:哈弗曼编码,是最优的变长编码

  

/*************************************************************************
        > File Name: Huffman_tree.c
        > Author: Luzelin
        > Mail: 
        > Created Time: 
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#define swap(a, b) { \
    __typeof(a) __temp = a; \
    a = b; b = __temp; \
}

typedef struct Node {
    char key;   //字符
    double rate;  //出现概率
    struct Node *lchild, *rchild;
} Node;

Node* GetNodeNode(char c, double val) {
    Node *s = (Node *)malloc(sizeof(Node));
    s->key = c;
    s->rate = val;
    s->lchild = s->rchild = NULL;
    return s;
}

//将data中最小的rate结点,放在n位置
void pick_min(Node **data, int n) {
    for (int i = n - 1; i >= 0; --i) {
        if (data[n]->rate > data[i]->rate) {
            swap(data[n], data[i]);
        }
    }
    return ;
}

//合并两个结点,并返回它们的父结点
Node* ComBinNode(Node *a, Node *b) {
    Node *s = (Node *)malloc(sizeof(Node));
    s->key = 0;
    s->rate = a->rate + b->rate;
    s->lchild = a;
    s->rchild = b;
    return s;
}

//将data指向的指针数组中的所有结点合并成一颗哈夫曼树
Node* GetHuffmanTree(Node **data, int n) {
    if (n == 0)  return NULL;
    //总共合并n-1次,n为结点数
    for (int i = 1; i < n; ++i) {
        //每次合并出现概率最小和次小的结点,并将其父结点存放至次小位置
        pick_min(data, n - i);
        pick_min(data, n - i - 1);
        data[n - i - 1] = ComBinNode(data[n - i], data[n - i - 1]);
    }
    //最终data[0]就是整棵Huffman树的根结点
    return data[0];
}

void __output(Node *root, char *str, int k) {
    str[k] = 0;
    if (root->lchild == NULL && root->rchild == NULL) {
        printf("%c : %f : %s\n", root->key, root->rate, str);
        return ;
    }
    str[k] = '0';
    __output(root->lchild, str, k + 1);
    str[k] = '1';
    __output(root->rchild, str, k + 1);
    return ;
}

void output(Node *root) {
    if (root == NULL)  return ;
    char str[100] = {0};
    __output(root, str, 0);
    return ;
}

void clear(Node *root) {
    if (root == NULL)  return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

int main() {
    int n;
    scanf("%d", &n);
    Node **data = (Node **)malloc(sizeof(Node *) * n);
    for (int i = 0; i < n; ++i) {
        char str[5];
        double val;
        scanf("%s%lf", str, &val);
        data[i] = GetNodeNode(str[0], val);
    }
    Node *root = GetHuffmanTree(data, n);
    output(root);
    clear(root);
    free(data);
    return 0;
}
input
26
A 0.034748
B 0.079101
C 0.001086
D 0.082359
E 0.060224
F 0.066238
G 0.047528
H 0.028817
I 0.006098
J 0.021216
K 0.010107
L 0.002339
M 0.042349
N 0.046525
O 0.004928
P 0.054962
Q 0.066154
R 0.062563
S 0.054043
T 0.049282
U 0.007601
V 0.014033
W 0.040428
X 0.071751
Y 0.019713
Z 0.025810


output:
N 0000
J 00010
Z 00011
G 0010
T 0011
S 0100
P 0101
I 0110000
U 0110001
V 011001
H 01101
E 0111
R 1000
Q 1001
F 1010
X 1011
A 11000
C 110010000
L 110010001
O 11001001
K 1100101
Y 110011
B 1101
D 1110
W 11110
M 11111
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值