数据结构与算法——树的应用(哈夫曼编码)
1. 哈夫曼编码概括
/*********************************************
* 本文图片较多,多刷新一下才能显示 *
**********************************************/
哈夫曼(Huffman)编码算法是基于二叉树构建编码压缩结构的,它是数据压缩中经典的一种算 法。算法根据文本字符出现的频率,重新对字符进行编码。
首先以下这段文字举例:
今天天气晴朗,我和乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫·马尔 尼·梅尔斯·帕特森·汤普森·华莱士·普雷斯顿出去玩!乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥 特·福克斯·伊维鲁莫·马尔尼·梅尔斯·帕特森·汤普森·华莱士·普雷斯顿贪玩,不小心摔了一跤,乔伊·亚 历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫·马尔尼·梅尔斯·帕特森·汤普森·华莱士·普 雷斯顿被摔得哇哇哭了,乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫·马尔尼·梅 尔斯·帕特森·汤普森·华莱士·普雷斯顿的爸爸闻声赶来,又把乔伊·亚历山大·比基·卡利斯勒·达夫·埃 利奥特·福克斯·伊维鲁莫·马尔尼·梅尔斯·帕特森·汤普森·华莱士·普雷斯顿痛扁了一阵。乔伊·亚历 山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫·马尔尼·梅尔斯·帕特森·汤普森·华莱士·普 雷斯顿的屁股都被揍扁了,因为乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫·马 尔尼·梅尔斯·帕特森·汤普森·华莱士·普雷斯顿把妈妈刚买给他的裤子弄破了!
这段文字中由于名字较长,整体篇幅增加了很多倍,如何将它们压缩呢?比如我们在文章的开头先声明一个缩写:
名字 | 缩写 |
乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫·马尔尼·梅 尔斯·帕特森·汤普森·华莱士·普雷斯顿 | 乔顿 |
那么,上面这段文字就可以缩成很小的一段:
今天天气晴朗,我和乔顿出去玩!乔顿贪玩,不小心摔了一跤,乔顿被摔得哇哇哭了,乔顿的爸爸闻声赶来, 又把小明痛扁了一阵。乔顿的屁股都被揍扁了,因为乔顿把妈妈刚买给他的裤子弄破了!
哈夫曼编码就是这样一个原理!按照文本字符出现的频率,出现次数越多的字符用越简短的编码 替代,因为为了缩短编码的长度,我们自然希望频率越高的词,编码越短,这样最终才能最大化 压缩存储文本数据的空间。
哈夫曼编码举例: 假设要对 “we will we will r u” 进行压缩。
压缩前,使用 ASCII 码保存:
119 | 101 | 32 | 119 | 105 | 108 | 108 | 32 | 119 | 101 |
32 | 119 | 105 | 108 | 108 | 32 | 114 | 32 | 117 |
共需: 19 个字节 - 152 个位保存
下面统计这句话中每个字符出现的频率。如下表,按频率高低已排序:
字符 | 空格 | w | l | e | i | r | u |
频率 | 5 | 4 | 4 | 2 | 2 | 1 | 1 |
再按照字符出现的频率,定制入戏下的编码表:(该编码表会用二叉树生成,后边会说明)
字符 | l | w | 空格 | e | i | u | r |
编码 | 00 | 01 | 10 | 110 | 1111 | 11100 | 11101 |
这样,“we will we will r u” 就可以按如下的位来保存:
01 110 10 01 1111 00 00 10 01 110 10 01 1111 00 00 10 11101 10 11100
2. 哈夫曼二叉树构建
2.1 按出现频率高低将其放入一个数组中,从左到右依次为频率逐渐增加
2.2 从左到右进行合并,依次构建二叉树。第一步取前两个字符 u 和 r 来构造初始二叉树,第一个 字符作为左节点,第二个元素作为右节点,然后两个元素相加作为新的空元素,并且两者权重相 加作为新元素的权重。
2.3 新节点加入后,依据权重重新排序,按照权重从小到大排列,上图已有序。
2.4 红色区域的新增元素可以继续和 i 合并,如下图所示:
2.5 合并节点后, 按照权重从小到大排列,如下图所示。
2.6 排序后,继续合并最左边两个节点,构建二叉树,并且重新计算新节点的权重
2.7 重新排序
2.8 重复上面步骤 6 和 7,直到所有字符都变成二叉树的叶子节点
3. 源码实现:
Huff.h
1 #pragma once 2 3 #include <stdio.h> 4 #include <assert.h> 5 #include <Windows.h> 6 #include <iostream> 7 #include <iomanip> 8 9 using namespace std; 10 11 #define MaxSize 1024 //队列的最大容量 12 13 typedef struct _Bnode 14 { 15 char value; 16 int weight; 17 struct _Bnode* parent; 18 struct _Bnode* lchild; 19 struct _Bnode* rchild; 20 } Btree, Bnode; /* 结点结构体 */ 21 22 typedef Bnode* DataType; //任务队列中元素类型 23 24 //结点结构 25 typedef struct _QNode 26 { 27 int priority; //每个节点的优先级,数值越大,优先级越高,优先级相同, 取第一个节点 28 DataType data; 29 struct _QNode* next; 30 }QNode; 31 32 typedef QNode* QueuePtr; 33 34 typedef struct Queue 35 { 36 int length; //队列的长度 37 QueuePtr front; //队头指针 38 QueuePtr rear; //队尾指针 39 }LinkQueue; 40 41 //队列初始化,将队列初始化为空队列 42 void InitQueue(LinkQueue* LQ) 43 { 44 if (!LQ) return; 45 LQ->length = 0; 46 LQ->front = LQ->rear = NULL; //把对头和队尾指针同时置 0 47 } 48 49 //判断队列为空 50 int IsEmpty(LinkQueue* LQ) 51 { 52 if (!LQ) return 0; 53 if (LQ->front == NULL) 54 { 55 return 1; 56 } 57 return 0; 58 } 59 60 //判断队列是否为满 61 int IsFull(LinkQueue* LQ) 62 { 63 if (!LQ) return 0; 64 if (LQ->length == MaxSize) 65 { 66 return 1; 67 } 68 return 0; 69 } 70 71 //入队,将元素 data 插入到队列 LQ 中 72 int EnterQueue(LinkQueue* LQ, DataType data, int priority) 73 { 74 if (!LQ) return 0; 75 if (IsFull(LQ)) 76 { 77 cout << "无法插入元素 " << data << ", 队列已满!" << endl; 78 return 0; 79 } 80 QNode* qNode = new QNode; 81 qNode->data = data; 82 qNode->priority = priority; 83 qNode->next = NULL; 84 85 if (IsEmpty(LQ)) {//空队列 86 LQ->front = LQ->rear = qNode; 87 } 88 else 89 { 90 qNode->next = LQ->front; 91 LQ->front = qNode; 92 //LQ->rear->next =qNode;//在队尾插入节点 qNode 93 //LQ->rear = qNode; //队尾指向新插入的节点 94 } 95 LQ->length++; 96 return 1; 97 } 98 99 //出队,遍历队列,找到队列中优先级最高的元素 data 出队 100 int PopQueue(LinkQueue* LQ, DataType* data) 101 { 102 QNode** prev = NULL, * prev_node = NULL; //保存当前已选举的最高优先级节点上一个节点的指针地址。 103 QNode* last = NULL, * tmp = NULL; 104 105 if (!LQ || IsEmpty(LQ)) 106 { 107 cout << "队列为空!" << endl; 108 return 0; 109 } 110 111 if (!data) return 0; 112 113 //prev 指向队头 front 指针的地址 114 prev = &(LQ->front); 115 116 //printf("第一个节点的优先级: %d\n", (*prev)->priority); 117 last = LQ->front; 118 tmp = last->next; 119 while (tmp) 120 { 121 if (tmp->priority < (*prev)->priority) 122 { 123 //printf("抓到个更小优先级的节点[priority: %d]\n", tmp->priority); 124 prev = &(last->next); 125 prev_node = last; 126 } 127 last = tmp; 128 tmp = tmp->next; 129 } 130 *data = (*prev)->data; 131 tmp = *prev; 132 *prev = (*prev)->next; 133 134 delete tmp; 135 LQ->length--; 136 137 //接下来存在 2 种情况需要分别对待 138 //1.删除的是首节点,而且队列长度为零 139 if (LQ->length == 0) 140 { 141 LQ->rear = NULL; 142 } 143 //2.删除的是尾部节点 144 if (prev_node && prev_node->next == NULL) 145 { 146 LQ->rear = prev_node; 147 } 148 return 1; 149 } 150 151 //打印队列中的各元素 152 void PrintQueue(LinkQueue* LQ) 153 { 154 QueuePtr tmp; 155 if (!LQ) return; 156 if (LQ->front == NULL) 157 { 158 cout << "队列为空!"; 159 return; 160 } 161 tmp = LQ->front; 162 while (tmp) 163 { 164 cout << setw(4) << tmp->data->value << "[" << tmp->priority << "]"; 165 tmp = tmp->next; 166 } 167 cout << endl; 168 } 169 170 //获取队首元素,不出队 171 int GetHead(LinkQueue* LQ, DataType* data) 172 { 173 if (!LQ || IsEmpty(LQ)) 174 { 175 cout << "队列为空!" << endl; 176 return 0; 177 } 178 if (!data) return 0; 179 *data = LQ->front->data; 180 return 1; 181 } 182 183 //清空队列 184 void ClearQueue(LinkQueue* LQ) 185 { 186 if (!LQ) return; 187 while (LQ->front) 188 { 189 QueuePtr tmp = LQ->front->next; 190 delete LQ->front; 191 LQ->front = tmp; 192 } 193 LQ->front = LQ->rear = NULL; 194 LQ->length = 0; 195 } 196 197 //获取队列中元素的个数 198 int getLength(LinkQueue* LQ) 199 { 200 if (!LQ) return 0; 201 return LQ->length; 202 }
哈夫曼编码实现.cpp
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <iostream> 4 #include "Huff.h" 5 6 using namespace std; 7 8 void PreOrderRec(Btree* root); 9 10 /* 构造哈夫曼编码树 */ 11 void HuffmanTree(Btree*& huff, int n) 12 { 13 LinkQueue* LQ = new LinkQueue; 14 int i = 0; 15 //初始化队列 16 InitQueue(LQ); 17 /* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */ 18 for (i = 0; i < n; i++) 19 { 20 Bnode* node = new Bnode; 21 cout << "请输入第" << i + 1 << "个字符和出现频率: " << endl; 22 cin >> node->value; //输入字符 23 cin >> node->weight;//输入权值 24 node->parent = NULL; 25 node->lchild = NULL; 26 node->rchild = NULL; 27 EnterQueue(LQ, node, node->weight); 28 } 29 PrintQueue(LQ); 30 //system("pause"); 31 //exit(0); 32 do 33 { 34 Bnode* node1 = NULL; 35 Bnode* node2 = NULL; 36 if (!IsEmpty(LQ)) 37 { 38 PopQueue(LQ, &node1); 39 printf("第一个出队列的数:%c, 优先级: %d\n", node1->value, 40 node1->weight); 41 } 42 else 43 { 44 break; 45 } 46 if (!IsEmpty(LQ)) 47 { 48 Bnode* node3 = new Bnode; 49 PopQueue(LQ, &node2); 50 printf("第二个出队列的数:%c, 优先级: %d\n", node2->value, node2->weight); 51 node3->lchild = node1; 52 node1->parent = node3; 53 node3->rchild = node2; 54 node2->parent = node3; 55 node3->value = ' '; 56 node3->weight = node1->weight + node2->weight; 57 printf("合并进队列的数:%c, 优先级: %d\n", node3->value, node3->weight); 58 EnterQueue(LQ, node3, node3->weight); 59 } 60 else 61 { 62 huff = node1; 63 break; 64 } 65 } while (1); 66 } 67 68 /************************************ 69 * 采用递归方式实现前序遍历 * 70 *************************************/ 71 void PreOrderRec(Btree* root) 72 { 73 if (root == NULL) 74 { 75 return; 76 } 77 printf("- %c -", root->value); 78 PreOrderRec(root->lchild); 79 PreOrderRec(root->rchild); 80 } 81 int main(void) 82 { 83 Btree* tree = NULL; 84 HuffmanTree(tree, 7); 85 PreOrderRec(tree); 86 system("pause"); 87 return 0; 88 }
====================================================================================================================