一、什么是哈夫曼编码:
哈夫曼编码是一种根据字符出现频率设计的变长编码方式,常用于数据压缩。它的基本思想是,将出现频率较高的字符用较短的编码表示,而出现频率较低的字符用较长的编码表示,从而减小编码的总长度。
二、如何构建哈夫曼编码:
(1)普通构建哈夫曼编码:
- 统计字符的频率,根据字符出现的频率高低构建字符频率表;
- 将字符频率表中的每个字符创建成一个单独的树节点;
- 从字符频率表中选择两个频率最低的节点,合并为一个新的节点,其频率为两个节点的频率之和,将这个新节点插入到字符频率表中;
- 重复步骤3,直到字符频率表中只剩下一个节点,即哈夫曼树的根节点;
- 对哈夫曼树进行遍历,左子树标记为0,右子树标记为1,标记每个字符对应的编码;
- 使用编码替换原来的字符,得到哈夫曼编码。
(2) 小顶堆构建哈夫曼编码:
- 统计字符的频率,根据字符出现频率的高低构建小顶堆;
- 将小顶堆中的每个字符创建成一个单独的树节点;
- 从小顶堆中依次弹出两个头节点(即两个频率最低的节点),合并为一个新的节点,其频率为两个节点的频率之和,将这个新节点重新插入到小顶堆中;
- 重复步骤3,直到小顶堆中只剩下一个节点,即哈夫曼树的根节点;
- 对哈夫曼树进行遍历,左子树标记为0,右子树标记为1,标记每个字符对应的编码;
- 使用编码替换原来的字符,得到哈夫曼编码。
(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;
}
六、运行效果图:
文章到此结束!