哈夫曼编码(小顶堆)

一、什么是哈夫曼编码:

哈夫曼编码是一种根据字符出现频率设计的变长编码方式,常用于数据压缩。它的基本思想是,将出现频率较高的字符用较短的编码表示,而出现频率较低的字符用较长的编码表示,从而减小编码的总长度。

二、如何构建哈夫曼编码: 

        (1)普通构建哈夫曼编码:
  1. 统计字符的频率,根据字符出现的频率高低构建字符频率表;
  2. 将字符频率表中的每个字符创建成一个单独的树节点;
  3. 从字符频率表中选择两个频率最低的节点,合并为一个新的节点,其频率为两个节点的频率之和,将这个新节点插入到字符频率表中;
  4. 重复步骤3,直到字符频率表中只剩下一个节点,即哈夫曼树的根节点;
  5. 对哈夫曼树进行遍历,左子树标记为0,右子树标记为1,标记每个字符对应的编码;
  6. 使用编码替换原来的字符,得到哈夫曼编码。
        (2) 小顶堆构建哈夫曼编码:
  1. 统计字符的频率,根据字符出现频率的高低构建小顶堆;
  2. 将小顶堆中的每个字符创建成一个单独的树节点;
  3. 从小顶堆中依次弹出两个头节点(即两个频率最低的节点),合并为一个新的节点,其频率为两个节点的频率之和,将这个新节点重新插入到小顶堆中;
  4. 重复步骤3,直到小顶堆中只剩下一个节点,即哈夫曼树的根节点;
  5. 对哈夫曼树进行遍历,左子树标记为0,右子树标记为1,标记每个字符对应的编码;
  6. 使用编码替换原来的字符,得到哈夫曼编码。
           (3)小顶堆构建如图:

         (4)构建哈夫曼编树如图:


 左0右1,频率最低的两个节点相结合,频率相加作为一个新节点,以此类推。

 三、使用小顶堆来优化构建哈夫曼树的优点:

  (1)使用小顶堆来优化构建哈夫曼树,能够提高代码的运行效率,同时缩短了该算法的时间复杂度。

(2)对于实现哈夫曼树的代码可以得到优化,简化代码,大大提高代码的可读性。

(3)淘汰掉了繁琐的应寻表操作而存在的循环操作。

四、代码实现 :

(1)创建一个结构体(作为树节点):
typedef struct Node {
    char ch;  //字符,也可以是数字
    int freq; //字符出现的频率
    struct Node *lchild, *rchild; //左、右子树
} Node;
(2)创建一个结构体(作为小顶堆节点):
typedef struct Heap { 
    Node **__data, **data; //__data,data分别为指针数组,__data指针数组偏移后的指针数组,类型为Node
    int n, size; //n表示数组中的元素个数,size表示数组的大小
} Heap;
 
(3)初始化树节点:
Node *getNewNode(int freq, char ch) {
    Node *p = (Node *)malloc(sizeof(Node)); //为指针p开辟空间,类型为Node
    p->ch = ch; //赋值
    p->freq = freq; //赋值
    p->lchild = p->rchild = NULL; //初始化左,右子树
    return p;
}
(4)初始化堆节点:
Heap *getNewHeap(int size) {
    Heap *h = (Heap *)malloc(sizeof(Heap)); //开辟指针h的存储空间,类型为Heap
    h->__data = (Node **)malloc(sizeof(Node *) * size); //开辟指针数据的存储空间
    h->data = h->__data - 1; //进行__data-1的偏移操作,使得data从1开始
    h->n = 0; //初始化n
    h->size = size; //赋值
    return h;
}
(5)向堆中插入元素:
#define swap(a, b) { \  //交换元素a,b
    __typeof(a) __c = (a); \  //将a的值赋给__c,__c的类型与a相同
    (a) = (b), (b) = __c; \  //将b的值赋给a,__c的值赋给b
}
int fullHeap(Heap *h) { //判断堆是否为满
    return h->n == h->size;
}
int cmpHeap(Heap *h, int i, int j) { //i为要判断的节点的下标,j为该节点的父节点
    return h->data[i]->freq < h->data[j]->freq; //小顶堆,子节点小于父节点,为真,要交换
}
void up_maintain(Heap *h, int i) { //向上调整
    while (i > 1 && cmpHeap(h, i, i / 2)) { //i>1为该树有子节点,cmpHeap比较子、父节点
        swap(h->data[i], h->data[i / 2]); //如果子节点小于父节点,交换
        i = i / 2; //从子节点向上调整到父节点(二叉树左(2*i),中(i),右(2*i+1)
    }
    return ;
}
int pushHeap(Heap *h, Node *n) { //插入数据
    if (fullHeap(h)) return 0; //判满
    h->n += 1; //每插入一个节点,n加一操作
    h->data[h->n] = n; //在指针数组中n的位置上插入节点n
    up_maintain(h, h->n); //向上调整
    return 1;
}
 
(6)从堆中弹出元素:
int emptyHeap(Heap *h) { //判空
    return h->n == 0;
}
Node *top(Heap *h) { //返回头节点
    if (emptyHeap(h)) return NULL; //判空
    return h->data[1]; //头节点
}
void down_maintain(Heap *h, int i) { //向下调整
    while (i * 2 <= h->n) { //向下调整的节点数不能超过数组中已有的节点数,i*2为左节点下标
        int ind = i, l = i * 2, r = i * 2 + 1; //ind为标记最小值,l为左节点下标,r为右节点下标
        if (cmpHeap(h, l, ind)) ind = l; //如果左节点的值小于父节点的值,标记l;
        if (r <= h->n && cmpHeap(h, r, ind)) ind = r; //如果如果右节点的值小于父节点的值,标记r;
        if (ind == i) return ; //如果上述条件都不满足,且满足此条件,则无需任何操作
        swap(h->data[i], h->data[ind]); //将下标为ind的节点与下标为i的节点交换
        i = ind; //从左或右子节点向下调整。
    }
    return ;
}
int popHeap(Heap *h) { //弹出元素
    if (emptyHeap(h)) return 0; //判空
    h->data[1] = h->data[h->n]; //将头节点弹出后,重新调整堆
    h->n -= 1; //弹出元素后,n-1操作
    down_maintain(h, 1);//向下调整,从头节点开始,将堆重新调整成小顶堆
    return 1;
}
(7)构建哈夫曼树:
 void clearHeap(Heap *h) { //释放空间
    if (h == NULL) return ;
    free(h->__data); //释放指针数组所占用的存储空间
    free(h); //释放指针所占用的存储空间
    return ;
}

int find_min_node(Node **node_arr, int n) { //寻找频率最小的节点的下标
    int ind = 0;
    for (int j = 1; j <= n; j++) {
        if (node_arr[ind]->freq > node_arr[j]->freq) ind = j; //标记为j
    }
    return ind;
}
 
Node *buildHaffmanTree(Node **node_arr, int n) { //构建哈夫曼树
    Heap *h = getNewHeap(n); //初始化小顶堆
    for (int i = 0; i < n; i++) pushHeap(h, node_arr[i]); //将所有节点压入堆中
    for (int i = 1; i < n; i++) {
        Node *node1 = top(h); //获取第一个头节点
        popHeap(h); //弹出操作
        Node *node2 = top(h); //获取第二个节点
        popHeap(h); //弹出操作
        Node *node3 = getNewNode(node1->freq + node2->freq, 0); //将两个节点的频率相加,作为新节点,字符初始化为0
        node3->lchild = node1; //新节点的左子树为第一个头节点
        node3->rchild = node2; //新节点的右节点为第二个头节点
        pushHeap(h, node3); //将新节点插入堆,继续寻找两个最小频率节点,再结合成新节点
    }
    Node *ret = top(h); //最后返回堆的头节点,即为哈夫曼树的头节点
    clearHeap(h);
    return ret;
}

(八)构建哈夫曼编码:

#define MAX_CHAR_NUM 128 //ASCII码
char *char_code[MAX_CHAR_NUM] = {0}; //初始化为0
 
void extractHaffmanCode(Node *root, char buff[], int k) { //第k个位置
    buff[k] = 0;//初始化
    if (root->lchild == NULL && root->rchild == NULL) { //如果该节点的左、右节点皆无,即叶子节点
        char_code[root->ch] = strdup(buff); //将buff复制给下标为root->ch(char变int)对应的字符
        return ;
    }
    buff[k] = '0'; //左零
    extractHaffmanCode(root->lchild, buff, k + 1); //递归左子树
    buff[k] = '1'; //右一
    extractHaffmanCode(root->rchild, buff, k + 1); //递归右子树
    return ;
}
 

五、完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define swap(a, b) { \
    __typeof(a) __c = a; \
    a = b, b = __c; \
}
typedef struct Node {
    char ch;
    int freq;
    struct Node *lchild, *rchild;
} Node;
typedef struct Heap {
    Node **__data, **data;
    int n, size;
} Heap;
Heap *getNewHeap(int size) {
    Heap *h = (Heap *)malloc(sizeof(Heap));
    h->__data = (Node **)malloc(sizeof(Node *) * size);
    h->data = h->__data - 1;
    h->n = 0;
    h->size = size;
    return h;
}
int fullHeap(Heap *h) {
    return h->n == h->size;
}
 
int emptyHeap(Heap *h) {
    return h->n == 0;
}
Node *top(Heap *h) {
    if (emptyHeap(h)) return NULL;
    return h->data[1];
}
int cmpHeap(Heap *h, int i, int j) {
    return h->data[i]->freq < h->data[j]->freq;
}
void up_maintain(Heap *h, int i) {
    while (i > 1 && cmpHeap(h, i, i / 2)) {
        swap(h->data[i], h->data[i / 2]);
        i = i / 2;
    }
    return ;
}
void down_maintain(Heap *h, int i) {
    while (i * 2 <= h->n) {
        int ind = i, l = i * 2, r = i * 2 + 1;
        if (cmpHeap(h, l, ind)) ind = l; 
        if (r <= h->n && cmpHeap(h, r, ind)) ind = r;
        if (ind == i) return ;
        swap(h->data[i], h->data[ind]);
        i = ind;
    }
    return ;
}
int pushHeap(Heap *h, Node *n) {
    if (fullHeap(h)) return 0;
    h->n += 1;
    h->data[h->n] = n;
    up_maintain(h, h->n);
    return 1;
}
int popHeap(Heap *h) {
    if (emptyHeap(h)) return 0;
    h->data[1] = h->data[h->n];
    h->n -= 1;
    down_maintain(h, 1);
    return 1;
}
void clearHeap(Heap *h) {
    if (h == NULL) return ;
    free(h->__data);
    free(h);
    return ;
}
Node *getNewNode(int freq, char ch) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->ch = ch;
    p->freq = freq;
    p->lchild = p->rchild = NULL;
    return p;
}
int find_min_node(Node **node_arr, int n) {
    int ind = 0;
    for (int j = 1; j <= n; j++) {
        if (node_arr[ind]->freq > node_arr[j]->freq) ind = j;
    }
    return ind;
}
Node *buildHaffmanTree(Node **node_arr, int n) {
    Heap *h = getNewHeap(n);
    for (int i = 0; i <n; i++) pushHeap(h, node_arr[i]);
    for (int i = 1; i < n; i++) {
        Node *node1 = top(h);
        popHeap(h);
        Node *node2 = top(h);
        popHeap(h);
        Node *node3 = getNewNode(node1->freq + node2->freq, 0);
        node3->lchild = node1;
        node3->rchild = node2;
        pushHeap(h, node3);
    }
    Node *ret = top(h);
    clearHeap(h);
    return ret;
}
void clear(Node *root) {
    if (root == NULL) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}
 
#define MAX_CHAR_NUM 128
char *char_code[MAX_CHAR_NUM] = {0};
void extractHaffmanCode(Node *root, char buff[], int k) {
    buff[k] = 0;
    if (root->lchild == NULL && root->rchild == NULL) {
        char_code[root->ch] = strdup(buff);
        return ;
    }
    buff[k] = '0';
    extractHaffmanCode(root->lchild, buff, k + 1);
    buff[k] = '1';
    extractHaffmanCode(root->rchild, buff, k + 1);
    return ;
}
int main() {
    char s[10];
    int n, freq;
    scanf("%d", &n);
    Node **node_arr = (Node **)malloc(sizeof(Node *) * n);
    for (int i = 0; i < n; i++) {
        scanf("%s%d", s, &freq);
        node_arr[i] = getNewNode(freq, s[0]);
    }
    Node *root = buildHaffmanTree(node_arr, n);
    char buff[1000];
    extractHaffmanCode(root, buff, 0);
    for (int i = 0; i < MAX_CHAR_NUM; i++) {
        if (char_code[i] == NULL) continue; //26个字符按顺序输出
        printf("%c : %s\n", i, char_code[i]);
    }
    free(*char_code);
    clear(root);
    return 0;
}

六、运行效果图: 


 文章到此结束!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值