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

33. 哈夫曼编码

时间:2023-06-19 18:23:13浏览次数:35  
标签:编码 elements return 哈夫曼 33 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,哈夫曼,33,char,str
From: https://www.cnblogs.com/kurome/p/17491844.html

相关文章

  • 哈夫曼树(Huffman Tree)的基本概念介绍
    哈夫曼树(HuffmanTree)是一种常用的数据结构,用于实现数据压缩和编码。它是由美国计算机科学家DavidA.Huffman于1952年提出的,被广泛应用于通信、压缩算法和信息存储等领域。哈夫曼树主要用于根据字符出现的频率构建最优的前缀编码,以便在压缩数据时能够有效地减少所需的比特数。该......
  • CVE-2023-33246命令执行复现分析
    RocketMQ是一款低延迟、高并发、高可用、高可靠的分布式消息中间件。既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。影响版本<=RocketMQ5.1.0<=RocketMQ4.9.5环境搭建dockerpullapache/rocketmq:4.......
  • Java 编码(一)Java实现SHA256算法
    本文实例讲述了JavaSHA-256加密的两种实现方法。分享给大家供大家参考,具体如下:参考文献 Java实现SHA256算法-自学java的小陈-博客园(cnblogs.com)1、利用Apache的工具类实现加密:maven:<dependency><groupId>commons-codec</groupId><artifactId>commons-codec</......
  • 如何找到抛出ORA-00933错误的SQL
    前几天上线,凌晨3点多打车回来的路上,兄弟联系我,提了一个问题,某核心系统,上线的时候,报了很多ORA-00933的错误,明显是应用写的SQL出现了错误导致的,但是因为未将出错的SQL打印到日志中,所以不知道究竟是什么SQL出错了,由于逻辑中涉及到很多的SQL,逐个排查,非常耗时。ORA-00933,意思是“SQLcom......
  • HTML实体编码用法介绍
    https://www.python100.com/html/5CF93176RGIS.html一、HTML实体编码是什么HTML实体编码是一种将特殊字符或符号转换为HTML代码的方法。由于HTML中一些字符具有特殊含义,因此使用特殊的代码表示这些字符可以避免与HTML标签发生冲突,保证页面正常显示。例如,"<"符号在HTML中表示开......
  • day 33 反射机制,元类,__new__,__call__,元类下的属性查找
    1,内置方法在满足某种条件下自动触发 2、python是动态,强类型的,解释型语言动态:在程序中定义变量时不需要定义变量的类型,在执行时才知道变量的类型;静态:必须定义好变量的类型。只要是动态语言,就必须有反射机制 解释:一句一句的翻译后执行强类型:3:反射实现反射机制的步骤1、......
  • 日文汉字和中文汉字编码一样吗
    https://www.zhihu.com/question/25273403https://zhidao.baidu.com/question/526124877723285885.html如果是Unicode或UTF8则一样如果是GB2312就算是同样的字也不一样,要看你是怎么设置的。这里需要涉及编码的问题日文编码JIS和简体中文GB中相同编码的字实际代表不同的字符......
  • 字符集与编码
    术语字符(character)是具有语义值的文本的最小单位。字符集(characterset)是可能由多种语言使用的字符的集合。例:拉丁语字符集由英语和大多数欧洲语言使用,但希腊语字符集仅由希腊语使用。编码字符集(codedcharacterset)是一个字符集,其中每个字符对应于一个唯一的数字。一......
  • 文件的编码和译码
    文件的编码和译码应用举例使用ascii码来编码使用哈夫曼编码编码输入各字符及其权值构造哈夫曼树--HT[i]进行哈夫曼编码--HC[I]查询HC[i],得到各字符串的哈夫曼编码解码构造哈夫曼树依次读入二进制码读入0,则走左孩子;读入1,则走右孩子一旦到达叶子结......
  • 循环码的编码、译码与循环冗余校验
    本专栏包含信息论与编码的核心知识,按知识点组织,可作为教学或学习的参考。markdown版本已归档至【Github仓库:<https://github.com/timerring/information-theory>】或者【AIShareLab】回复信息论获取。循环码的编码循环码编码用硬件实现时,可用除法电路来实现。除法电路主要是......