Fork me on GitHub

数据结构与算法——树的应用(哈夫曼编码)

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 }

 

 

 

 

 

 

 

 

====================================================================================================================

 

posted @ 2020-11-25 01:29  索智源  阅读(334)  评论(0编辑  收藏  举报