哈夫曼编码
之前帮同学做的一个哈夫曼编码的课设,有些功能还没有完善。
虽然是一个看起来简单的哈夫曼,但写起来也查阅了很多资料,比如数据难以树状打印,最后无可奈何选择了横向输出。还有文件操作的一些问题,开始保存时我直接将 stdout 的输出目标替换成文件,简便了不少,但是后来发现在文件操作结束后,将 stdout 的目标重新设为控制台会有bug,程序结束会出现乱码,百度后用 dup 和 dup2 复制句柄解决。
根据字母频度自动编码还没有写,这个功能先鸽了。
短短的百行代码让我再途中也有过想放弃,我意识到一个工程是多么冗杂和困难,自己要学的还有很多,要走的路也还很长,但我一定会坚持下来的。
1 H#include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <algorithm>
5 #include <fstream>
6 #include <iomanip>
7 #include <io.h>
8 #define MaxN 100
9 using namespace std;
10
11 typedef struct
12 {
13 string data; //原字母
14 string code; //对应码字
15 double value; //权值
16 int lchild,rchild; //左右孩子
17 int deep; //深度
18 int flag; //用于判断是否合并
19
20 }hufftree;
21
22 class huffman
23 {
24
25 public:
26 void create(); //创建哈夫曼树
27 void huffcode(); //创建哈夫曼编码
28 void huffdfs(int); //深度优先遍历
29 void print(); //输出哈夫曼树
30 void printree(int,int,int); //树状递归输出
31 void translate_text(); //编码
32 void translate_code(); //译码
33 void inithuff(); //初始化
34 void huffmerge(int,int); //合并节点
35 int getmin(); //获取当前最小权值节点的下标
36 void changeline(); //换行
37 private:
38 hufftree tr[2*MaxN]; //创建节点
39 int N=0; //叶子节点数
40 int M=0; //总结点数
41 int time=0; //记录当前步骤
42 //c11开始支持类内初始化,之前版本可能报错
43 };
44
45 void huffman::create()
46 {
47 system("cls"); //清屏
48 inithuff();
49 int step,flagg=0;
50 do
51 {
52 if(flagg)
53 cout<<"输入有误,请重新输入:";
54 else
55 {
56 changeline();
57 cout<<"1.输入待编码字母及权值"<<endl;
58 cout<<"2.使用预设样例测试"<<endl;
59 changeline();
60 cout<<"请选择:";
61 }
62 cin>>step;
63 flagg=1;
64 }
65 while(step!=1 && step!=2);
66 system("cls");
67 cin.sync(); //清空缓存区
68 if(step==1)
69 {
70 cout<<"请输入待编码字母及其权值,用回车隔开(输入0结束输入)"<<endl;
71 for(int i=0;i<MaxN;i++)
72 {
73 char temp[50];
74 double val=0;
75 cin.getline(temp,50);
76 cin.sync();
77 if(temp[0]=='0'&&temp[1]=='\0') //输入0结束输入
78 break;
79 cin>>val;
80 cin.sync();
81 tr[N].data=temp;
82 tr[N++].value=val;
83 }
84 M=N;
85 }
86 if(step==2)
87 {
88 string let[MaxN]={" ","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
89 double val[MaxN]={0.2,0.063,0.0105,0.023,0.035,0.105,0.0221,0.011,0.047,0.054,0.001,0.003,0.029,0.021,0.059,0.0644,0.0175,0.001,0.053,0.052,0.071,0.0225,0.008,0.012,0.002,0.012,0.001};
90 for(int i=0;i<27;i++)
91 {
92 tr[N].data=let[i];
93 tr[N++].value=val[i];
94 }
95 M=N;
96 }
97 time=1;
98 changeline();
99 cout<<"输入完毕,共"<<N<<"组数据"<<endl;
100 if(N!=0)
101 {
102 huffcode();
103 changeline();
104 int ifsave=0,flaag=0;
105 do
106 {
107 if(flaag)
108 cout<<"输入有误,请重新输入:";
109 else
110 {
111 cout<<"是否保存至文件"<<endl;
112 cout<<"1.是"<<endl
113 <<"2.否"<<endl;
114 }
115 flaag=1;
116 cin>>ifsave;
117 }while(ifsave!=1 && ifsave!=2);
118 system("cls");
119 if(ifsave==1)
120 {
121 /*-----------------------------------------------
122 将输出对象从控制台转换到文件,保存后再回到控制台
123 ------------------------------------------------*/
124 #define STDOUT 1
125 int oldstdout=dup(STDOUT);
126 FILE *fp;
127 if(freopen("huffcode.txt","w",stdout)==NULL)
128 {
129 cout<<"创建\"huffcode.txt\"失败"<<endl;
130 return;
131 }
132 cout<<std::left<<setw(10)<<"字母"<<setw(10)<<"权值"<<setw(10)<<"编码"<<endl;
133 for(int i=0;i<N;i++)
134 {
135 if(tr[i].data==" ")
136 cout<<setw(10)<<"空格"<<setw(10)<<tr[i].value<<setw(10)<<tr[i].code<<endl;
137 else
138 cout<<setw(10)<<tr[i].data<<setw(10)<<tr[i].value<<setw(10)<<tr[i].code<<endl;
139 }
140 freopen("CON","w",stdout);
141 dup2(oldstdout,STDOUT);
142 close(oldstdout);
143 system("cls");
144 cout<<"已保存至\"huffcode.txt\""<<endl;
145 }
146 }
147
148 }
149
150 int huffman::getmin()
151 {
152 /*-----------------------------------------------
153 遍历搜索权值最小的节点,若权值相同,取深度较小者
154 -------------------------------------------------*/
155 double minval=10000;
156 int minnum=-1,mindeep=0;
157 for(int i=0;i<M;i++)
158 {
159 if(tr[i].flag==0) //忽略已使用的节点
160 {
161 if(minval>tr[i].value)
162 {
163 minval=tr[i].value;
164 mindeep=tr[i].deep;
165 minnum=i;
166 }
167 else if(minval==tr[i].value)
168 {
169 if(mindeep>tr[i].deep)
170 {
171 minval=tr[i].value;
172 mindeep=tr[i].deep;
173 minnum=i;
174 }
175 }
176 }
177 }
178 tr[minnum].flag=1;
179 return minnum;
180 }
181
182 void huffman::inithuff()
183 {
184 for(int i=0;i<2*MaxN;i++)
185 {
186 tr[i].data="\0";
187 tr[i].code="\0";
188 tr[i].lchild=-1;
189 tr[i].rchild=-1;
190 tr[i].value=0;
191 tr[i].deep=1;
192 tr[i].flag=0;
193 }
194 N=0,M=0;
195 }
196
197 void huffman::huffcode()
198 {
199 /*-----------------------------------
200 获取两个权值最小节点的下标并将其合并
201 ------------------------------------*/
202 for(int i=1;i<N;i++)
203 {
204 int m1=getmin(),m2=getmin();
205 huffmerge(m1,m2);
206 }
207 /*-----------------------------------
208 找打仅剩的一个未使用的节点,即根节点
209 -------------------------------------*/
210 for(int i=0;i<M;i++)
211 {
212 if(tr[i].flag==0)
213 huffdfs(i);
214 }
215 cout<<"哈夫曼树建立完成"<<endl;
216 cout<<std::left<<setw(10)<<"字母"<<setw(10)<<"权值"<<setw(10)<<"编码"<<endl;
217 for(int i=0;i<N;i++)
218 {
219 if(tr[i].data==" ")
220 cout<<setw(10)<<"空格"<<setw(10)<<tr[i].value<<setw(10)<<tr[i].code<<endl;
221 else
222 cout<<setw(10)<<tr[i].data<<setw(10)<<tr[i].value<<setw(10)<<tr[i].code<<endl;
223 }
224 }
225
226 void huffman::huffmerge(int t1,int t2)
227 {
228 /*---------------------
229 合并权值,标明左右孩子
230 -----------------------*/
231 tr[M].value=tr[t1].value+tr[t2].value;
232 tr[M].deep=max(tr[t1].deep,tr[t2].deep)+1;
233 tr[M].lchild=t1,tr[M].rchild=t2;
234 M++;
235 }
236
237 void huffman::print()
238 {
239 system("cls");
240 changeline();
241 if(time<1)
242 cout<<"未初始化,请先初始化再打印"<<endl;
243 else
244 {
245 int maxdeep=0;
246 for(int i=0;i<M;i++) //找出最大深度
247 {
248 if(maxdeep<tr[i].deep)
249 maxdeep=tr[i].deep;
250 }
251 for(int i=0;i<M;i++)
252 {
253 if(tr[i].flag==0)
254 {
255 cout<<"深度 ";
256 for(int j=1;j<=maxdeep;j++)
257 cout<<j<<" ";
258 cout<<endl;
259 printree(i,1,0);
260 }
261 }
262 }
263 changeline();
264 int ifsave=0,flaag=0;
265 do
266 {
267 if(flaag)
268 cout<<"输入有误,请重新输入:";
269 else
270 {
271 cout<<"是否保存至文件"<<endl;
272 cout<<"1.是"<<endl
273 <<"2.否"<<endl;
274 }
275 flaag=1;
276 cin>>ifsave;
277 }while(ifsave!=1 && ifsave!=2);
278 system("cls");
279 if(ifsave==1)
280 {
281 #define STDOUT 1
282 int oldstdout=dup(STDOUT);
283 FILE *fp;
284 if(freopen("hufftree.txt","w",stdout)==NULL)
285 {
286 cout<<"创建\"hufftree.txt\"失败"<<endl;
287 return;
288 }
289 int maxdeep=0;
290 for(int i=0;i<M;i++)
291 {
292 if(maxdeep<tr[i].deep)
293 maxdeep=tr[i].deep;
294 }
295 for(int i=0;i<M;i++)
296 {
297 if(tr[i].flag==0)
298 {
299 cout<<"深度 ";
300 for(int j=1;j<=maxdeep;j++)
301 cout<<j<<" ";
302 cout<<endl;
303 printree(i,1,0);
304 }
305 }
306 freopen("CON","w",stdout);
307 dup2(oldstdout,STDOUT);
308 close(oldstdout);
309 system("cls");
310 cout<<"已保存至\"hufftree.txt\""<<endl;
311 }
312 }
313
314 /*-------------------------------------------
315 三个参数:
316 t为节点下标
317 line为行数,便于控制树状输出
318 dirct为该节点是左孩子还是右孩子,1是右,2是左
319 ---------------------------------------------*/
320 void huffman::printree(int t,int line,int dirct)
321 {
322 if(tr[t].code=="\0" && tr[t].value==0)
323 {
324 return;
325