1+ #include < stdio.h>
2+ #include < stdlib.h>
3+ #include < string.h>
4+ #include " data_structure.h"
5+
6+ /*
7+ 根据给定的n个权值构造一棵赫夫曼树,wet中存放n个权值
8+ */
9+ HuffmanTree create_HuffmanTree (int *wet,int n)
10+ {
11+ // 一棵有n个叶子节点的赫夫曼树共有2n-1个节点
12+ int total = 2 *n-1 ;
13+ HuffmanTree HT = (HuffmanTree)malloc (total*sizeof (HTNode));
14+ if (!HT)
15+ {
16+ printf (" HuffmanTree malloc faild!" );
17+ exit (-1 );
18+ }
19+ int i;
20+
21+ // 以下初始化序号全部用-1表示,
22+ // 这样在编码函数中进行循环判断parent或lchild或rchild的序号时,
23+ // 不会与HT数组中的任何一个下标混淆而造成误判
24+
25+ // HT[0],HT[1]...HT[n-1]中存放需要编码的n个叶子节点
26+ for (i=0 ;i<n;i++)
27+ {
28+ HT[i].parent = -1 ;
29+ HT[i].lchild = -1 ;
30+ HT[i].rchild = -1 ;
31+ HT[i].weight = *wet;
32+ wet++;
33+ }
34+
35+ // HT[n],HT[n+1]...HT[2n-2]中存放的是中间构造出的每棵二叉树的根节点
36+ for (;i<total;i++)
37+ {
38+ HT[i].parent = -1 ;
39+ HT[i].lchild = -1 ;
40+ HT[i].rchild = -1 ;
41+ HT[i].weight = 0 ;
42+ }
43+
44+ int min1,min2; // 用来保存每一轮选出的两个weight最小且parent为0的节点
45+ // 每一轮比较后选择出min1和min2构成一课二叉树,最后构成一棵赫夫曼树
46+ for (i=n;i<total;i++)
47+ {
48+ select_minium (HT,i,min1,min2);
49+ HT[min1].parent = i;
50+ HT[min2].parent = i;
51+ // 这里左孩子和右孩子可以反过来,构成的也是一棵赫夫曼树,只是所得的编码不同
52+ HT[i].lchild = min1;
53+ HT[i].rchild = min2;
54+ HT[i].weight =HT[min1].weight + HT[min2].weight ;
55+ }
56+ return HT;
57+ }
58+
59+ /*
60+ 从HT数组的前k个元素中选出weight最小且parent为-1的两个,分别将其序号保存在min1和min2中
61+ */
62+ void select_minium (HuffmanTree HT,int k,int &min1,int &min2)
63+ {
64+ min1 = min (HT,k);
65+ min2 = min (HT,k);
66+ }
67+
68+ /*
69+ 从HT数组的前k个元素中选出weight最小且parent为-1的元素,并将该元素的序号返回
70+ */
71+ int min (HuffmanTree HT,int k)
72+ {
73+ int i = 0 ;
74+ int min; // 用来存放weight最小且parent为-1的元素的序号
75+ int min_weight; // 用来存放weight最小且parent为-1的元素的weight值
76+
77+ // 先将第一个parent为-1的元素的weight值赋给min_weight,留作以后比较用。
78+ // 注意,这里不能按照一般的做法,先直接将HT[0].weight赋给min_weight,
79+ // 因为如果HT[0].weight的值比较小,那么在第一次构造二叉树时就会被选走,
80+ // 而后续的每一轮选择最小权值构造二叉树的比较还是先用HT[0].weight的值来进行判断,
81+ // 这样又会再次将其选走,从而产生逻辑上的错误。
82+ while (HT[i].parent != -1 )
83+ i++;
84+ min_weight = HT[i].weight ;
85+ min = i;
86+
87+ // 选出weight最小且parent为-1的元素,并将其序号赋给min
88+ for (;i<k;i++)
89+ {
90+ if (HT[i].weight <min_weight && HT[i].parent ==-1 )
91+ {
92+ min_weight = HT[i].weight ;
93+ min = i;
94+ }
95+ }
96+
97+ // 选出weight最小的元素后,将其parent置1,使得下一次比较时将其排除在外。
98+ HT[min].parent = 1 ;
99+
100+ return min;
101+ }
102+
103+ /*
104+ 从叶子节点到根节点逆向求赫夫曼树HT中n个叶子节点的赫夫曼编码,并保存在HC中
105+ */
106+ void HuffmanCoding (HuffmanTree HT,HuffmanCode &HC,int n)
107+ {
108+ // 用来保存指向每个赫夫曼编码串的指针
109+ HC = (HuffmanCode)malloc (n*sizeof (char *));
110+ if (!HC)
111+ {
112+ printf (" HuffmanCode malloc faild!" );
113+ exit (-1 );
114+ }
115+
116+ // 临时空间,用来保存每次求得的赫夫曼编码串
117+ // 对于有n个叶子节点的赫夫曼树,各叶子节点的编码长度最长不超过n-1
118+ // 外加一个'\0'结束符,因此分配的数组长度最长为n即可
119+ char *code = (char *)malloc (n*sizeof (char ));
120+ if (!code)
121+ {
122+ printf (" code malloc faild!" );
123+ exit (-1 );
124+ }
125+
126+ code[n-1 ] = ' \0 ' ; // 编码结束符,亦是字符数组的结束标志
127+ // 求每个字符的赫夫曼编码
128+ int i;
129+ for (i=0 ;i<n;i++)
130+ {
131+ int current = i; // 定义当前访问的节点
132+ int father = HT[i].parent ; // 当前节点的父节点
133+ int start = n-1 ; // 每次编码的位置,初始为编码结束符的位置
134+ // 从叶子节点遍历赫夫曼树直到根节点
135+ while (father != -1 )
136+ {
137+ if (HT[father].lchild == current) // 如果是左孩子,则编码为0
138+ code[--start] = ' 0' ;
139+ else // 如果是右孩子,则编码为1
140+ code[--start] = ' 1' ;
141+ current = father;
142+ father = HT[father].parent ;
143+ }
144+
145+ // 为第i个字符的编码串分配存储空间
146+ HC[i] = (char *)malloc ((n-start)*sizeof (char ));
147+ if (!HC[i])
148+ {
149+ printf (" HC[i] malloc faild!" );
150+ exit (-1 );
151+ }
152+ // 将编码串从code复制到HC
153+ strcpy (HC[i],code+start);
154+ }
155+
156+ free (code); // 释放保存编码串的临时空间
157+ }
158+
159+ /*
160+ 从根节点到叶子节点无栈非递归遍历赫夫曼树HT,求其中n个叶子节点的赫夫曼编码,并保存在HC中
161+ */
162+ void HuffmanCoding2 (HuffmanTree HT,HuffmanCode &HC,int n)
163+ {
164+ // 用来保存指向每个赫夫曼编码串的指针
165+ HC = (HuffmanCode)malloc (n*sizeof (char *));
166+ if (!HC)
167+ {
168+ printf (" HuffmanCode malloc faild!" );
169+ exit (-1 );
170+ }
171+
172+ // 临时空间,用来保存每次求得的赫夫曼编码串
173+ // 对于有n个叶子节点的赫夫曼树,各叶子节点的编码长度最长不超过n-1
174+ // 外加一个'\0'结束符,因此分配的数组长度最长为n即可
175+ char *code = (char *)malloc (n*sizeof (char ));
176+ if (!code)
177+ {
178+ printf (" code malloc faild!" );
179+ exit (-1 );
180+ }
181+
182+ int cur = 2 *n-2 ; // 当前遍历到的节点的序号,初始时为根节点序号
183+ int code_len = 0 ; // 定义编码的长度
184+
185+ // 构建好赫夫曼树后,把weight用来当做遍历树时每个节点的状态标志
186+ // weight=0表明当前节点的左右孩子都还没有被遍历
187+ // weight=1表示当前节点的左孩子已经被遍历过,右孩子尚未被遍历
188+ // weight=2表示当前节点的左右孩子均被遍历过
189+ int i;
190+ for (i=0 ;i<cur+1 ;i++)
191+ {
192+ HT[i].weight = 0 ;
193+ }
194+
195+ // 从根节点开始遍历,最后回到根节点结束
196+ // 当cur为根节点的parent时,退出循环
197+ while (cur != -1 )
198+ {
199+ // 左右孩子均未被遍历,先向左遍历
200+ if (HT[cur].weight == 0 )
201+ {
202+ HT[cur].weight = 1 ; // 表明其左孩子已经被遍历过了
203+ if (HT[cur].lchild != -1 )
204+ { // 如果当前节点不是叶子节点,则记下编码,并继续向左遍历
205+ code[code_len++] = ' 0' ;
206+ cur = HT[cur].lchild ;
207+ }
208+ else
209+ { // 如果当前节点是叶子节点,则终止编码,并将其保存起来
210+ code[code_len] = ' \0 ' ;
211+ HC[cur] = (char *)malloc ((code_len+1 )*sizeof (char ));
212+ if (!HC[cur])
213+ {
214+ printf (" HC[cur] malloc faild!" );
215+ exit (-1 );
216+ }
217+ strcpy (HC[cur],code); // 复制编码串
218+ }
219+ }
220+
221+ // 左孩子已被遍历,开始向右遍历右孩子
222+ else if (HT[cur].weight == 1 )
223+ {
224+ HT[cur].weight = 2 ; // 表明其左右孩子均被遍历过了
225+ if (HT[cur].rchild != -1 )
226+ { // 如果当前节点不是叶子节点,则记下编码,并继续向右遍历
227+ code[code_len++] = ' 1' ;
228+ cur = HT[cur].rchild ;
229+ }
230+ }
231+
232+ // 左右孩子均已被遍历,退回到父节点,同时编码长度减1
233+ else
234+ {
235+ HT[cur].weight = 0 ;
236+ cur = HT[cur].parent ;
237+ --code_len;
238+ }
239+
240+ }
241+ free (code);
242+ }
0 commit comments