• 哈夫曼树与哈夫曼编码


    哈弗曼树与哈弗曼编码

    一、前置知识

    什么是编码

    ‘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 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    python使用appium打开程序后,为什么没有操作后程序就自动退出了
    J9数字论:Web 3.0与区块链驱动的未来
    Windows系统下使用tar命令,压缩文件与解压缩文件并指定路径
    MySQL日志管理和权限管理(重点)
    【2022-05-31】JS逆向之易企秀
    RabbitMQ 的延时队列和镜像队列原理与实战
    MySQL数据库备份实战
    代碼隨想錄算法訓練營|第五十二天|123 买卖股票的最佳时机III、188 买卖股票的最佳时机IV。刷题心得(c++)
    【Node】使用Node.js构建简单的静态页面生成器
    LeetCode·每日一题·764.最大加号标志·动态规划
  • 原文地址:https://blog.csdn.net/qq_40342400/article/details/128159998