这篇随笔主要是Huffman编码,构建哈夫曼树有各种各样的实现方法,如优先队列,数组构成的树等,但本质都是堆。
这里我用数组来存储数据,以堆的思想来构建一个哈弗曼树,并存入vector中,进而实现哈夫曼编码
步骤: 1生成哈夫曼树 (取最小权值树和次小权值树生成新树,排列后重新取树,不断重复)
2编码 (遵循左零右一的原则)
3解码(是编码的逆向,本文还未实现,日后有机会补充)
data.txt 测试数据:
5 |
结果:
下面贴代码:
1 #include <iostream>
2 #include <fstream>
3 #include <algorithm>
4 #include <vector>
5 #include <array>
6
7 using namespace std;
8
9 #define ARR_SIZE 100 //缓冲区大小
10
11 typedef struct Tree
12 {
13 int freq;
14 char key = '\0';
15 Tree *left, *right;
16 Tree()
17 {
18 freq = 0;
19 key = '\0';
20 left = NULL;
21 right = NULL;
22 }
23 } Tree, *pTree;
24 union key_or_point
25 {
26 char key;
27 pTree point;
28 };
29 enum infor_type
30 {
31 key_s,
32 point_s
33 };
34 class infor
35 {
36 public:
37 int freq;//权值
38 key_or_point kp;//记录键值或者 新生成的树的地址
39 infor_type type;// 联合体key_or_point的类型由infor_type标志
40 infor()
41 {
42 freq = 0;
43 kp.key = NULL;
44 type = key_s;
45 }
46 };
47
48 array<infor, ARR_SIZE> arr;//用来读取要处理的数据
49 vector<pTree> trees; //所有生成的树都放在vector里面
50
51 int num; //要处理的数据个数
52
53 bool cmp(infor a, infor b)
54 {
55 return a.freq > b.freq;
56 }
57
58 void Huffman()
59 {
60 //找出最小权值和次小权值
61 sort(&arr[0], &arr[num], cmp);
62 int cal = num - 1;
63 while (cal > 0)
64 {
65
66 pTree pta = new Tree();
67 vector<pTree>::iterator it;
68
69 pTree ptl = new Tree();
70 ptl->freq = arr[cal].freq;
71 // pt all 的左子树
72 if (arr[cal].type == point_s)
73 {
74 pta->left = arr[cal].kp.point;//如果存放的是地址,那么该树已入vector
75 //无需重复操作
76 }
77 else
78 {
79 ptl->key = arr[cal].kp.key;
80 trees.push_back(ptl);
81 it = trees.end() - 1;
82 pta->left = *it;
83 }
84
85
86 pTree ptr = new Tree();
87 ptr->freq = arr[cal - 1].freq;
88 // pt all 的右子树
89 if (arr[cal - 1].type == point_s)
90 {
91 pta->right = arr[cal - 1].kp.point; //如果存放的是地址,那么该树已入vector
92 //无需重复操作
93 }
94 else
95 {
96 ptr->key = arr[cal - 1].kp.key;
97 trees.push_back(ptr);
98 it = trees.end() - 1;
99 pta->right = *it;
100 }
101
102 pta->freq = arr[cal].freq + arr[cal - 1].freq;
103 trees.push_back(pta);//pt all 本树
104
105 it = trees.end() - 1;
106 arr[cal - 1].kp.point = *it;
107 arr[cal - 1].type = point_s;//保存新生成树的地址
108
109 arr[cal - 1].freq = arr[cal - 1].freq + arr[cal ].freq;
110 //最小权值的树和次权值的树组成新树后,放回原数组
111 //新树的key_or_point此时类型变为point_s指针指向vector存放的位置
112
113 //第一次循环会有三棵树入vector,重新排列后,新树无需重复入vector
114 cal--;
115 sort(&arr[0], &arr[cal + 1], cmp);
116
117 }
118
119 }
120
121 void traversTree(pTree pt, string st = "")
122 {
123 //中序遍历二叉树
124 //遵循左0右1的原则
125 if (pt->left == NULL && pt->right == NULL)
126 {
127 cout.flags(ios::left);
128 cout.width(10);
129 cout << st.c_str() << " ";
130 cout << pt->key << endl;
131 return;
132 }
133 if (pt->left != NULL)
134 {
135 st += '0';
136 traversTree(pt->left, st);
137 st.pop_back();//从左边出来后要回退一个字符,避免进入右边时多出一个字符
138 }
139
140 if (pt->right != NULL)
141 {
142 st += '1';
143 traversTree(pt->right, st);
144 }
145 return ;
146 }
147
148 void printCode()
149 {
150 vector<pTree>::iterator it;
151 it = trees.end() - 1;
152 pTree pt = *it; //取出最顶端的树
153 cout << "print HuffmanCode:" << endl;
154 traversTree(pt);
155 }
156 int main()
157 {
158 ifstream filein("data.txt");
159 cin.rdbuf(filein.rdbuf());//重定向输入
160 cin >> num;//要处理的数据个数
161 for (int i = 0; i < num; i++)
162 {
163 cin >> arr[i].freq;
164 }
165 for (int i = 0; i < num; i++)
166 {
167 cin >> arr[i].kp.key;
168 }
169 Huffman();
170 printCode();
171 return 0;
172 }
分析:
这是以上测试数据生成的树的情况。
只有叶子节点表示有效的符号,所以遍历树时返回条件是叶子节点(如果是叶子节点则返回)
总结:
1 编程时用的一些小技巧总结:
1.1 输出调试信息:可以采用如下方式
#ifdef DEBUG
cout调试信息....
#endif
1.2 联合体union需要取得类型时,可以加一个enum来记录和标志uninon的类型
2 编程方法反思:
可以看到源码中用到了两次sort,这是省事的做法了。
目前想到的改进的方法是用二分插入(数据已经排序)
对比起来,我觉得优先队列的方式更易懂且效率更高,但此文也算是一次小探索,值得记录下来
3 感想:
本人入园第一次随笔,如有不足或错误,还望指出。
以上
原文出处:https://www.cnblogs.com/virgildevil/p/10349693.html