对于题目和规则不做赘述,进入正题:
要点:
1.逻辑上哈夫曼树是树结构,但程序的物理结构是数组。
2.树构建好后,规定一下左右是0还是1,然后遍历树的每条路径,即可得到每个数据的编码结果。
代码如下:
//哈夫曼编码
typedef struct mesage//在优先级队列中用到的结构体
{
mesage(int index = 0, int weight = 0)
:index(index), weight(weight)
{}
int index;//下标,用来标识是哪个叶节点
int weight;//权重,根据这个调整堆
operator int() const//相当于(int)mesage,用权重做比较
{
return weight;
}
}Mesage;
typedef struct node//静态二叉树的节点信息
{
node(int value = 0, int parent = 0, int left = 0, int right = 0)
:value(value), parent(parent), left(left), right(right)
{}
Mesage msg;//下标和权重
int value;//数值
int parent;//双亲的下标
int left;//左孩子的下标
int right;//右孩子的下标
}Node;
typedef struct result//存储一个值的最终哈夫曼编码
{
result(int value = 0)
:value(value)
{
str = new char[20]();
}
~result(){delete[] str;}
int value;//值
char *str;//编码
}Result;
void displayTree(Node *table, int len)//显示静态哈夫曼树
{
printf("index\tweight\tvalue\tparent\tleft\tright\n");
for (int i = 0; i < len; ++i)
{
printf("%d\t%d\t%d\t%d\t%d\t%d\n",
table[i].msg.index, table[i].msg.weight, table[i].value,
table[i].parent, table[i].left, table[i].right);
}
}
//给最终结果填写数据
void fillData(Node *table, int index_table, Result *result, int index_result,
char tempSpace[], int index_tempSpace)
{
if (table[index_table].value != 0)//到达叶子节点,填写数据
{
for (int i = 0; i < index_tempSpace; ++i)
result[index_result].str[i] = tempSpace[i];
result[index_result].str[index_tempSpace] = '\0';
result[index_result].value = table[index_table].value;
return;
}
tempSpace[index_tempSpace] = '0';//向左走记为0
fillData(table, table[index_table].left, result, table[index_table].left,
tempSpace, index_tempSpace+1);
tempSpace[index_tempSpace] = '1';//向右走记为1
fillData(table, table[index_table].right, result, table[index_table].right,
tempSpace, index_tempSpace+1);
}
void displayCode(Node *table, int len, int dataNumber)//根据静态哈夫曼树计算并显示结果
{
Result *result = new Result[dataNumber+1];//最终结果数组
char *tempSpace = new char[dataNumber+1];//辅助空间字符串,记录往左右走时的0和1
fillData(table, len-1, result, 1, tempSpace, 0);
for (int i = 1; i <= dataNumber; ++i)//打印结果
{
printf("value= %d, code= %s\n", result[i].value, result[i].str);
}
delete[] result;
delete[] tempSpace;
}
//根据数据的权重和数值,构建静态哈夫曼树
void HuffmanTree(const int weight[], const int value[], int len)
{
Node *table = new Node[2*len]();//用数组存储哈夫曼树结构
for (int i = 1; i <= len; ++i)//填充叶子节点数据,从下标1开始
{
table[i].msg = Mesage(i, weight[i-1]);
table[i].value = value[i-1];
}
//构建树过程:把叶子节点都先放到优先级队列中,设定堆为小顶堆
priority_queue<Mesage, vector<Mesage>, greater<Mesage>> que;
for (int i = 0; i < len; ++i)
que.push(Mesage(i+1, weight[i]));
//从集合中取两个权重最小的子树,给它们创建一个双亲节点,然后把新的子树放入集合中
//循环这一过程,直到把所有叶子节点全部合并到树上,树构建完成
int offest = len+1;
for (int i = 0; i < len-1; ++i)
{
int index = offest + i;
Mesage left = que.top();
que.pop();
Mesage right = que.top();
que.pop();
table[index].msg.index = index;
table[index].msg.weight = left.weight + right.weight;
table[index].left = left.index;
table[index].right = right.index;
table[left.index].parent = index;
table[right.index].parent = index;
que.push(Mesage(index, table[index].msg.weight));
}
displayTree(table, 2*len);
cout << endl;
displayCode(table, 2*len, len);
delete[] table;
}
int main()
{
int weight[] = {8,3,4,1,5,9,6,7,2};//数据权重
int len = sizeof(weight) / sizeof(weight[0]);
int *value = new int[len]();//数据值
for (int i = 0; i < len; ++i)
value[i] = weight[i] * 10;
HuffmanTree(weight, value, len);
delete[] value;
return 0;
}
结果如下:(简单起见,数据的值和权重一致,权重=值/10)(可见树的辅助节点=叶子节点-1,最终树节点个数=数据个数*2-1。因此创建数组时大小确定,为数据的2倍。先把9个数据放在前面,然后构建树时辅助节点依次放在后面。)(数据节点的左孩子和右孩子为空,辅助节点的值为空。)
下面以一个小规模情况说明逻辑过程:
假设
weight={2,1,3}
value={20,10,30}
1.先把全部数据都放到集合中。(圆圈内部是数据,下方是权重)
2.拿出权重最小的两个子树:1和2,组成树
3.把树放入集合中
4.拿出两个权重最小的两个子树:3和3,组成树
5.集合为空,树构建完成。
6.遍历树(左0右1)
解题完成,
10的哈夫曼编码是:00
20的哈夫曼编码是:01
30的哈夫曼编码是:1
可以看到,数据权重越大,越靠近跟节点,哈夫曼编码的长度越小,越节省空间,所以哈夫曼编码在压缩算法中广泛应用,压缩效果良好。