c++代码实现哈夫曼树的创建、编码以及求WPL (顺序结构)
文章目录
exercise problem
构造哈夫曼树生产哈夫曼编码,并求出带权路径长度(WPL)。
code
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
#define ElemType string
#define INF 0x3f3f3f3f
typedef char** HuffCode;
int N;
typedef struct huffNode {
double weight; //权重
int lchild, rchild, parent; //左右子节点和父节点
} HTNode, *HuffTree;
//找出剩下节点中权值最小的两个节点下标,分别用s1和s2保存
void select(const HuffTree &HT, int n, int &s1, int &s2);
//翻转字符串
void reverseChars(char *chs, int len);
//HT:哈夫曼树,HC:哈夫曼编码,w:构造哈夫曼树节点的权值,n:构造哈夫曼树节点的个数
void createHT(HuffTree &HT, HuffCode &HC, double *w, int n);
//输出各元素对应的哈夫曼编码
void showHuffCode(ElemType data[], HuffCode HC);
//求带权路径长度
int getWPL(HuffTree &HT, int idx, int depth);
int main() {
N = 15; //共15个有效元素
//第0个元素保留不用
ElemType data[N] = {"0","the","of","a","to","and","in","that","he","is","at","on","for","His","are","be"};
//第0个元素保留不用
double w[N] = {0,1192, 677, 541, 518, 462, 450, 242, 195, 190, 181, 174, 157, 138, 124, 123};
HuffTree HT;
HuffCode HC;
createHT(HT, HC, w, N);
cout << "Huffcode:\n";
showHuffCode(data, HC);
printf("\n");
cout << "The weighted path length of the HuffmanTree is " << getWPL(HT, N * 2 - 1, 0) << "\n\n";
system("pause");
return 0;
}
//找出数组中权值最小的两个节点下标,分别用s1和s2保存
void select(const HuffTree &HT, int n, int &s1, int &s2) {
s1 = s2 = 0;
double min1 = INF; //最小值
double min2 = INF; //次小值
for (int i = 1; i <= n; i++) {
//筛选没有父节点的最小和次小权值下标
if (HT[i].parent == 0) {
//如果比最小值小
if (HT[i].weight < min1) {
//更新次小值
min2 = min1;
s2 = s1;
//更新最小值
min1 = HT[i].weight;
s1 = i;
}
//如果大于等于最小值,且小于次小值
else if ((HT[i].weight >= min1) && (HT[i].weight < min2)) {
//只更新次小值
min2 = HT[i].weight;
s2 = i;
}
}
}
}
//翻转字符串
void reverseChars(char *chs, int len) {
for (int i = 0; i < len / 2; i++) {
char temp = chs[i];
chs[i] = chs[len-1-i];
chs[len-1-i] = temp;
}
}
//HT:哈夫曼树,HC:哈夫曼编码,w:构造哈夫曼树节点的权值,n:构造哈夫曼树节点的个数
void createHT(HuffTree &HT, HuffCode &HC, double *w, int n) {
int s1, s2, m = 2 * n - 1; //m: n个节点构造的哈夫曼树是2n-1个节点
char* code; //暂存编码
HT = new HTNode[m+1]; //0单元未使用
for (int i = 1; i <= n; i++) {
//初始化前n个节点 (构造哈夫曼树的原始节点)
HT[i] = {w[i], 0, 0, 0};
}
for (int i = n+1; i <= m; i++) {
//初始化后n-1个节点 (创建最小两节点的父节点)
HT[i] = {0, 0, 0, 0};
}
//构建哈夫曼树
for (int i = n+1; i <= m; i++) {
//找出前i-1个节点中权值最小的节点下标
select(HT, i - 1, s1, s2);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
//哈夫曼编码
HC = new char*[n];
//暂存编码,使用了0单元
code = new char[n];
for (int i = 1; i <= n; i++) {
//k: 当前节点,用于求对应的编码(0或1), f: k的父节点, j: 记录编码在字符数组code的位置
int k = i, f = HT[k].parent, j = 0;
//从叶子扫描到根
while (f != 0) {
if (HT[f].lchild == k) {
code[j] = '0';
}
else if (HT[f].rchild == k) {
code[j] = '1';
}
k = HT[k].parent;
f = HT[k].parent;
j++;
}
//标记末尾位置
code[j] = '\0';
reverseChars(code, j);
//将暂存编码转移到HC[]中存储
HC[i] = new char[n];
strcpy(HC[i], code);
}
}
void showHuffCode(ElemType data[], HuffCode HC) {
for (int i = 1; i <= N; i++) {
cout << data[i] << ": " << HC[i] << "\n";
}
}
int getWPL(HuffTree &HT, int idx, int depth) {
//dfs遍历直到该节点是叶子节点
if(HT[idx].lchild == 0 && HT[idx].rchild == 0) {
return HT[idx].weight * depth;
}
return getWPL(HT, HT[idx].lchild, depth + 1) + getWPL(HT, HT[idx].rchild, depth + 1);
}
running result
summary
注意:
1. 在对code进行赋值,将要结束的时候加上code[j] = '\0';以标记末尾位置, 以'\0'结尾是char数组的特点
2. strcpy是cstring库的函数,只能操作char * 类型的数据
3. 在数据数组data[], 权值数组w[] 中0单元不使用
4. typedef char ** HuffCode;
HuffCode 实际上是一个字符串数组, 相当于string[], 它的第i个元素代表下标为i的节点对应的哈夫曼编码
思考:
1. 为什么在data[], w[]中第0个元素保留不用?
在初始化哈夫曼树时,会给节点赋值HT[i] = {w[i]或0, 0, 0, 0},以标记该节点没有孩子节点和头结点。
在创建哈夫曼树时,有if(HT[f].lchild(或rchild) == k)的判断语句,如果使用-1而不是0进行初始化,则会导致数组下标越界(array subscript -1 is below array bounds of ...)。
这个问题可以在判断语句中加上 -1的情况先进行判断,从而解决。
2. 为什么不在 struct huffNode 加入成员变量 ElemType elem 表示数据?
可以加入,使得huffNode承载的信息更加具体。
但我个人觉得,在创建哈夫曼树的过程中,并未使用到数据,而仅仅是根据权值。
数据和权值对应着不同数组的相同下标,并不会影响到编码的使用。
3. 为什么 struct huffNode 中既要记录lchild、rchild,也要记录parent?
在获取数据(叶子节点)的哈夫曼编码的过程,要从叶子节点往上遍历直至根节点,所以需要parent成员变量,以进行遍历操作