首页 > 其他分享 >32. 哈夫曼编码

32. 哈夫曼编码

时间:2023-06-17 17:47:15浏览次数:43  
标签:编码 elements return 哈夫曼 32 char str

一、什么是哈夫曼编码

  我们可以用哈夫曼树得到哈夫曼编码,即字符集中每个字符作为一个叶子节点,各个字符出现的频率作为节点的权值,根据上述方法构造哈夫曼树。因为哈夫曼树不唯一,因此哈夫曼编码也不唯一。哈夫曼编码广泛用于数据文件的压缩,其压缩效率通常在 20%~90% 之间。哈夫曼码是可变字长编码(VLC)的一种。哈夫曼编码也被称为最佳编码。

二、哈夫曼编码的原理

  1. 统计字符串中的各个字符出现的次数;
  2. 按照上面出现的次数构建一颗哈夫曼树,次数作为权值;
  3. 根据哈夫曼树,给各个字符规定编码(前缀编码),向左的路径为 0,向右的路径为 1;
  4. 根据上面的哈夫曼编码对字符串进行变长编码;

三、最小堆的相关操作

3.1、最小堆的表示

typedef struct HeapStruct *MinHeap;
typedef struct HeapStruct *Heap;
typedef struct TreeNode *HuffmanTree;
typedef HuffmanTree Position;
typedef struct TreeNode ElementType;

struct HeapStruct
{
    ElementType *elements;      // 存储堆元素的数组
    int size;                   // 堆的当前元素个数
    int capacity;               // 堆的最大容量
};

struct TreeNode
{
    char value;                 // 字符
    int weight;                 // 节点权值
    Position left;              // 指向左子节点
    Position right;             // 指向右子节点
};

3.2、最小堆的建立

/**
 * @brief 最小堆的创建
 * 
 * @param maxSize 堆的最大容量
 * @return MinHeap 返回指向最小堆的指针
 */
MinHeap CreateMinHeap(int maxSize)
{
    MinHeap H = malloc(sizeof(struct HeapStruct));
    if(H == NULL)
    {
        printf("分配内存失败!\n");
        return NULL;
    }

    H->elements = calloc((maxSize+1),sizeof(ElementType));
    if(H->elements == NULL)
    {
        printf("分配内存失败!\n");
        return NULL;
    }
  
    H->size = 0;
    H->capacity = maxSize;
    // 定义哨兵为小于堆中所有可能元素的值,便于以后更快操作
    H->elements[0].value = '\0';
    H->elements[0].weight = MinData;
    H->elements[0].left = NULL;
    H->elements[0].right = NULL;

    return H;
}

3.3、最小堆的插入

/**
 * @brief 最小堆的插入
 * 
 * @param H 待操作的最小堆
 * @param item 待插入的元素
 */
void Insert(MinHeap H,ElementType *item)
{
    int i;

    if(H->size == H->capacity)
    {
        printf("最小堆已满\n");
        return;
    }

    i = ++H->size;                                              // i指向插入后堆中的最后一个元素的位置
    for(; H->elements[i/2].weight > item->weight; i /= 2)       // 向下过滤结点
    {
        H->elements[i] = H->elements[i/2];
    }
  
    H->elements[i] = *item;                                     // 将item插入
}

3.4、最小堆的删除

/**
 * @brief 最小堆的删除
 * 
 * @param H 待操作的最小堆
 * @return ElementType 返回要删除的元素(根节点)
 */
ElementType * DeleteMin(MinHeap H)
{
    int parent,child;
    ElementType *minItem = malloc(sizeof(ElementType));
    ElementType temp;

    if(H->size == 0)
    {
        printf("最小堆已经空了\n");
        return NULL;
    }

    // 取出根结点最小值
    minItem->weight = H->elements[1].weight;
    minItem->value = H->elements[1].value;
    minItem->left = H->elements[1].left;
    minItem->right = H->elements[1].right;

    temp = H->elements[H->size--];                                          // 用最小堆中最后一个元素从根节点开始向上过滤下层结点
  
    for(parent = 1; parent*2 <= H->size; parent = child)                    // parent*2 <= H->size判别该节点是否有左孩子
    {
        child = parent * 2;                                                 // child指向左孩子
        if((child != H->size) &&                                            // child==H->size意味着左儿子是最后一个元素
            (H->elements[child].weight > H->elements[child+1].weight))      // 如果左孩子比右孩子大
        {
            child++;                                                        // child指向指向右孩子
        }
        if(temp.weight <= H->elements[child].weight)                        // temp元素比左右儿子都小
        {
            break;
        }
        else                                                                // 移动temp元素到下一层
        {
            H->elements[parent] = H->elements[child];                       // 把左右儿子中小的那个拷贝到parent上
        }
    }
    H->elements[parent] = temp;
  
    return minItem;
}

3.5、最小堆的调整

/**
 * @brief 将一个堆转换为小顶堆
 *
 * @param Heap 待操作的堆
 */
MinHeap BuildMinHeap(Heap H)
{
    int i = 0, j = 0;
    int temp = 0;

    for(i = H->size/2; i >= 1; i--)
    {
        AdjustHeap(H,i,H->size);
    }

    return H;
}
/**
 * @brief 将以i对应的非叶子节点的树调整为小顶堆
 * 
 * @param array 待调整的数组
 * @param i 表示非叶子节点在数组中的索引
 * @param n 待调整的数据的元素的个数
 */
void AdjustHeap(Heap H,int i,int n)
{
    ElementType temp = H->elements[i];
    int k = 0;
    // k=i*2,k是i节点的左子节点
    for(k = i*2; k <= n; k = k*2)
    {
        if(k+1 <= n && H->elements[k].weight > H->elements[k+1].weight)     // 左子节点的值大于右子节点的值
        {
            k++;                                                            // k指向右子节点
        }
        if(H->elements[k].weight < temp.weight)                             // 如果子节点小于父节点
        {
            H->elements[i] = H->elements[k];                                // 把较大的值赋值给当前节点
            i = k;                                                          // i指向k,继续循环比较
        }
        else
        {
            break;
        }
    }
    // 当for循环结束后,已经将以i为父节点的树的最小值,放在了最顶上,即局部小顶堆
    H->elements[i] = temp;                                                  //将temp放在调整后的位置
}

四、字符编码

4.1、字符编码的表示

typedef struct CodingMap *CodeMap;
typedef struct Maping *Map;

struct CodingMap
{
    Map map;                    // 键值对,用于存储字符编码的键值对
    int count;                  // 用于存储键值对的个数
};

struct Maping
{
    char key;                   // 用于存放字符本身
    int value;                  // 用于存储字符出现的次数
};

4.2、生成字符编码

 统计字符串中的各个字符出现的次数;

/**
 * @brief 获取字符编码函数
 * 
 * @param str 待编码的字符串
 * @return CodeMap 返回字符编码的键值对 
 */
CodeMap GetCodeMap(char str[])
{
    int length = strlen(str);
    int i = 0;
    char chCode[128] = {0};

    CodeMap codeMap = malloc(sizeof(struct CodingMap));
    if(codeMap == NULL)
    {
        printf("分配内存失败!\n");
        return NULL;
    }

    codeMap->map = calloc(128,sizeof(struct Maping));
    if(codeMap->map == NULL)
    {
        printf("分配内存失败!\n");
        return 0;
    }

    codeMap->count = 0;

    for(i = 0; i < length; i++)
    {
        chCode[str[i]]++;                           // 统计字符个数
    }

    for(i = 0; i < 128; i++)
    {
        if(chCode[i] != 0)
        {
            codeMap->map[codeMap->count].key = i;
            codeMap->map[codeMap->count].value = chCode[i];
            codeMap->count++;
        }
    }

    return codeMap;
}

五、哈夫曼树的构建

  按照字符出现的次数构建一颗哈夫曼树,次数作为权值;

/**
 * @brief 哈夫曼树的构造
 * 
 * @param H 根据结点创建最小堆
 * @return HuffmanTree 返回执行树根的指针
 */
HuffmanTree CreateHuffmanTree(CodeMap codeMap)
{
    // 假设H->size个权值已经存在H->elements[]里
    int i;
    HuffmanTree T;

    MinHeap H = CreateMinHeap(MinSize);
    for(i = 0; i < codeMap->count; i++)
    {
        H->elements[i+1].value = codeMap->map[i].key;
        H->elements[i+1].weight = codeMap->map[i].value;
        H->elements[i+1].left = NULL;
        H->elements[i+1].right = NULL;
        H->size++;
    }

    H = BuildMinHeap(H);                                    // 将H->elements[]按权值调整为最小堆

    while(H->size > 1)                                      // 做H->size-1次合并
    {
        T = malloc(sizeof(ElementType));                    // 建立新节点
        if(T == NULL)
        {
            printf("分配内存失败!\n");
            return NULL;
        }

        T->left = DeleteMin(H);
        T->right = DeleteMin(H);
        T->value = '\0';
        T->weight = T->left->weight + T->right->weight;     // 计算新权值

        Insert(H,T);                                        // 将新T插入最小堆
    }

    T = DeleteMin(H);

    return T;
}

六、哈夫曼编码

6.1、哈夫曼编码的表示

typedef struct HuffmanCodingMap *HuffmanCodeMap;
typedef struct HuffmanMapping *HuffmanMap;

struct HuffmanCodingMap
{
    HuffmanMap map;             // 键值对,用于存储哈夫曼编码的键值对
    int count;                  // 用于存储键值对的个数
};

struct HuffmanMapping
{
    char key;                   // 用于存放字符本身
    char *value;                // 用于存储哈夫曼编码
};

6.2、生成哈夫曼编码表

  根据哈夫曼树,给各个字符规定编码(前缀编码),向左的路径为 0,向右的路径为 1;

/**
 * @brief 获取哈夫曼编码表
 * 
 * @param tree 待操作的哈夫曼树
 * @return HuffmanCodeMap 返回哈夫曼编码表
 */
HuffmanCodeMap GetHuffmanEncode(HuffmanTree tree)
{
    char str[100] = {0};

    if(tree == NULL)
    {
        printf("哈夫曼树为空!\n");
        return NULL;
    }
    GetHuffmanCodeMap(tree->left,"0",str);
    GetHuffmanCodeMap(tree->right,"1",str);

    return huffmanCodeMap;
}
/**
 * @brief 哈夫曼编码表的核心算法
 * 
 * @param tree 待操作的哈夫曼树
 * @param code 哈夫曼树的左路径为 0,右路径为 1
 * @param str 递归生成哈夫曼编码
 */
void GetHuffmanCodeMap(HuffmanTree tree,char code[],char str[])
{
    static int num = 0;
    char string[100];

    strcpy(string,str);
    strcat(string,code);

    if(tree != NULL)
    {
        // 判断当前节点是叶子节点还是非叶子节点
        if(tree->value == '\0')                                     // 非叶子节点
        {
            // 递归处理
            GetHuffmanCodeMap(tree->left,"0",string);              // 向左递归
            GetHuffmanCodeMap(tree->right,"1",string);             // 向右递归
        }
        else                                                        // 叶子节点
        {
            huffmanCodeMap->map[num].key = tree->value;
            huffmanCodeMap->map[num].value = malloc(sizeof(strlen(string)));
            strcpy(huffmanCodeMap->map[num].value,string);
            num++;
        }
    }
}

七、数据压缩

typedef struct HuffmanEncoding *HuffmanEncode;

struct HuffmanEncoding
{
    int length;
    char *HuffmanCodeByte;
    int lastBit;
};
/**
 * @brief 哈夫曼压缩函数
 * 
 * @param str 原始字符串
 * @return HuffmanEncode 返回编码后的字符数组
 */
HuffmanEncode HuffmanZip(char *str)
{
    int i = 0;

    CodeMap codeMap = GetCodeMap(str);                                          // 获取字符编码

    HuffmanTree tree = CreateHuffmanTree(codeMap);                              // 获取哈夫曼树

    huffmanCodeMap = malloc(sizeof(struct HuffmanCodingMap));
    if(huffmanCodeMap == NULL)
    {
        printf("分配内存失败!\n");
        return 0;
    }
    huffmanCodeMap->count = codeMap->count;
    huffmanCodeMap->map = calloc(huffmanCodeMap->count,sizeof(struct HuffmanMapping));
    if(huffmanCodeMap->map == NULL)
    {
        printf("分配内存失败!\n");
        return 0;
    }

    huffmanCodeMap = GetHuffmanEncode(tree);                                    // 根据哈夫曼树获取哈夫曼编码

    HuffmanEncode huffmanEncode = Zip(str,huffmanCodeMap);                      // 根据哈夫曼编码对原始字符串进行压缩

    return huffmanEncode;
}
/**
 * @brief 将字符串进行编码
 * 
 * @param src 原始的字符串
 * @param huffmanCode 哈夫曼编码表
 * @return HuffmanEncode 返回编码后的字符数组
 */
HuffmanEncode Zip(char * src,HuffmanCodeMap huffmanCode)
{
    int i = 0, j = 0;
    char str[1024] = {0};                               // 哈夫曼编码对应的字符串

    // 利用哈夫曼编码表将原始字符串转换成哈夫曼编码对应的字符串
    for(i = 0; i < strlen(src); i++)                    // 遍历源数组
    {
        for(j = 0; j < huffmanCode->count; j++)
        {
            if(src[i] == huffmanCode->map[j].key)
            {
                strcat(str,huffmanCode->map[j].value);
                break;
            }
        }
    }

    int len = (strlen(str) + 7) / 8;                    // 统计返回哈夫曼编码的长度
    int index = 0;                                      // 记录第一个byte

    HuffmanEncode huffmanEncode = malloc(sizeof(struct HuffmanEncoding));
    if(huffmanEncode == NULL)
    {
        printf("分配内存失败!\n");
        return NULL;
    }
    huffmanEncode->length = len;
    huffmanEncode->lastBit = strlen(str) % 8 ? strlen(str) % 8 : 8;
    huffmanEncode->HuffmanCodeByte = calloc(huffmanEncode->length,sizeof(char));

    // 将字符串转换为字节数组,每8位对应一个byte,所以步长为8
    for(i = 0; i < strlen(str); i += 8)
    {
        huffmanEncode->HuffmanCodeByte[index] = 0;

        char strByte[9] = {0};
        if(i+8 > strlen(str))                           // 不够8位
        {
            strcpy(strByte,str+i);
        }
        else
        {
            strncpy(strByte,str+i,8);
        }
      

        for(j = 0; j < strlen(strByte); j++)
        {
            if(strByte[j] == '1')
            {
                huffmanEncode->HuffmanCodeByte[index] += (int)pow(2,strlen(strByte)-j-1);
            }
        }
        index++;
    }
  
    return huffmanEncode;
}

八、数据的解压

  数据解压的思路:

  1. 将哈夫曼字节数组 huffmanEncode 先转成哈夫曼编码对应的二进制字符串。
  2. 将哈夫曼编码对应的二进制字符串对照哈夫曼编码表重新转成字符串;
/**
 * @brief 根据哈夫曼编码进行解码
 * 
 * @param huffmanCode 哈夫曼编码表
 * @param huffmanEncode 压缩后的字节数组
 * @param encodeStr 解压后的字符串
 * @return char* 返回指向解压后的字符串的首地址
 */
char * Unzip(HuffmanCodeMap huffmanCode,HuffmanEncode huffmanEncode,char encodeStr[])
{
    int i = 0, j = 0;
    char str[9] = {0};
    char binStr[huffmanEncode->length * 8];
    int num = 0;

    // 先得到huffmanEncode对应的二进制字符串
    // 循环处理前length-1个编码的字节数组
    for(i = 0; i < huffmanEncode->length - 1; i++)
    {
        memset(str,0,9);
        byteToBitString(huffmanEncode->HuffmanCodeByte[i],8,str);
        strcat(binStr,str);
    }
    // 处理最后一个编码的字节数组
    memset(str,0,9);
    byteToBitString(huffmanEncode->HuffmanCodeByte[i],huffmanEncode->lastBit,str);
    strcat(binStr,str);

    // 把二进制字符串按照哈夫曼编码表进行解码
    for(i = 0; i < strlen(binStr);)
    {
        char count = 1;
        char flag = 1;
        char b = 0;

        while(flag)
        {
            char key[100] = {0};
            strncpy(key,binStr+i,count);                            // i不动,count移动,指定匹配一个字符
            for(j = 0; j < huffmanCode->count; j++)
            {
                if(strcmp(key,huffmanCode->map[j].value) == 0)      // 如果匹配到
                {
                    b = huffmanCode->map[j].key;
                    // printf("%s:%c\n",key,b);
                    flag = 0;
                    break;
                }
            }
            if(flag)                                                // 如果没有匹配到
            {
                count++;
            }
        
        }

        encodeStr[num++] = b;
        i += count;
    }

    // 当for循环结束后,encodeStr[]存放了所有的字符
    return encodeStr;
}
/**
 * @brief 将一个字节数组转换为对应的二进制字符串
 * 
 * @param byte 待操作的字节数组
 * @param bit 字节中的位数
 * @param str 转换的二进制字符串
 * @return char* 返回执行二进制字符串的首地址
 */
char * byteToBitString(char byte,char bit,char str[])
{
    int i = 0;

    for(i = 0; i < bit; i++)
    {
        str[i] = ((byte >> (bit - 1 - i)) & 1) + '0';
    }

    return str;
}

九、测试程序

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

HuffmanCodeMap huffmanCodeMap;

int main(void)
{
    char str[] = "The past belongs to death, and that the future belongs to yourself.";                    // 原始字符串
    int i = 0;
    char encodeStr[100];

    HuffmanEncode huffmanEncode = HuffmanZip(str);                              // 根据哈夫曼编码对原始字符串进行压缩

    Unzip(huffmanCodeMap,huffmanEncode,encodeStr);
    printf("%s\n",encodeStr);

    return 0;
}

标签:编码,elements,return,哈夫曼,32,char,str
From: https://www.cnblogs.com/kurome/p/17487766.html

相关文章

  • Python 字符编码转换(转载)
    Python字符编码转换1.在python2默认编码是ASCII,python3里默认是unicode2.unicode分为utf-32(占4个字节),utf-16(占两个字节),utf-8(占1-4个字节),soutf-16就是现在最常用的unicode版本,不过在文件里存的还是utf-8,因为utf8省空间3.在py3中encode,在转码的同时......
  • CF1732D1 题解
    CF1732D1Balance题解题目解释输入一个\(op\)和\(x\),\(op\)有\(2\)种情况。\(op\)为+,则将\(x\)加入到集合中。\(op\)为?,则找到一个最小的\(k\),使\(k\timesx\)不在合集中。题目非常简单明了。具体实现这时,看到这句话:\(1\lex\le10^{18}\),所以不可......
  • Office Visio中文(英文)破解版64位/32位软件 软件大全
    MicrosoftVisio2019是Office软件系列中的负责绘制流程图和示意图的软件,是一款便于IT和商务人员就复杂信息、系统和流程进行可视化处理、分析和交流的软件。可以帮助用户创建具有专业外观的图表,以便理解、记录和分析信息、数据、系统和过程,促进对系统和流程的了解,深入了解复杂信......
  • STM32之外部中断/时间控制器(EXTI)
    一、EXTI管理控制23个中断/事件,每个中断/事件都对应一个边沿检测器,可以实现信号输入的上升沿检测和下降沿检测。EXTI可实现对每个中断/事件线单独配置,可以单独配置为中断或事件,以及触发事件的属性。二、EXTI的功能框图,见具体资料手册。三、EXIT中断/事件线#defineEXTI_Li......
  • 基于STM32的铁路自动围栏系统设计
    一、项目背景随着城市规模的不断扩大和交通运输方式的日益发展,铁路与公路的交叉口已经成为常见的场景。然而,这些交叉口往往存在一定的安全隐患,因为有时不易发现列车行进的情况,导致公路上的车辆或行人可能会无意中闯入铁路区域,从而引发重大交通事故。为了解决这个问题,当前开发了一款......
  • 白名单rundll32加载shellcode上线metasploit(nim学习系列)
    白名单rundll32加载shellcode上线metasploit监听metasploitmsfconsole-x"useexploits/multi/handler;setlhost192.168.0.101;setlport443;setpayloadwindows/x64/meterpreter/reverse_tcp;exploit"生成shellcodemsfvenom-pwindows/x64/meterpreter/r......
  • 申威3231服务器Redis性能验证-及最全信创CPU性能分析
    申威3231服务器Redis性能验证-及最全信创CPU性能分析背景公司里面新进了几台服务器.有台申威服务器.因为前段时间参与过一次申威的POC验证.当时对性能有一点简单的理解.但是因为不方便,没有测试更多.这次有了一台实体机器,并且可以上网,所以感觉可以方便的多了.本来想......
  • RK3588(YD-88)瑞芯微 Rockchip RK3588 开发板套件,支持8G内存,32G eMMC存储
     一、产品简介1.产品简述:YD-88 是基于瑞芯微RK3588 的一款核心板RK3588是一颗高性能、低功耗的应用处理器芯片,专为ARMPC、边缘计算、个人移动互联网设备和其它多媒体应用而设计,是由4个A76和4个A55与独立的NEON协处理器集成的。RK3588内置了多种功能强大的嵌入式......
  • Beamr:CABR(闭环内容自适应编码解决方案)
    ContentAwareABR技术本文将简要介绍一下编码优化领域的一位新贵—Beamr的技术动态。Beamr是内容自适应视频编码与优化解决方案的提供商,致力于为MSO(Multi-SystemOperator,多系统运营商)和OTT(OverTheTop,流媒体服务商)提供视频技术支持,如Hollywoodstudios以及视频......
  • 代码随想录算法训练营第九天| 232.用栈实现队列 225. 用队列实现栈
    232.用栈实现队列注意:1,构造函数不需要2,需要有两个成员变量inout代码:1classMyQueue{2public:3stack<int>in;4stack<int>out;5MyQueue(){67}89voidpush(intx){10in.push(x);11}1213intpop(){1......