(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹)
目录
哈夫曼树的定义
假设有 m个权值 {𝒘1 ,𝒘 2, ··· , 𝒘 m } 可以构造一棵含n个叶子结点的二叉树,每个叶子结点的权为𝒘 𝒊 ,则其中带权路径长度 WPL最下的二叉树称作最优二叉树或哈夫曼树
路径:a→b的路径:a→ 𝒘 2 、 𝒘 2 → 𝒘 𝟐 、 𝒘 1 → 𝒘 3 、 𝒘 𝟒 → 𝐛
路径长度:a→b的路径长度:4
树的路径长度:从树根到每一结点的路径长度之和权:对实体的某个或某些属性的数值化描述
结点的带权路径长度为从该结点到树根之间的路径长度与结点上权的乘积
数的带权路径长度为树中所有叶子结点的带权路径长度之和
/** Huffman树结点 */
typedef struct haffNode {
char data; //用来存放字符的数据域
int weight; //权重
struct haffNode* leftChild; //左孩子
struct haffNode* rightChild; //右孩子
}HaffNode;
/**以顺序结构存储的树结点,依次构建编码解码的字符映射表 */
HaffNode node[MAX_SIZE];
构造哈夫曼树
1.根据给定的n个权值{w 1 ,w 2 ,……w n },构造n棵只有根结点的二叉树。
2.在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置新二叉树根
结点权值为其左右子树根结点权值之和。
3.在森林中删除这两棵树,同时将新得到的二叉树加入森林中。
重复上述两步,直到只含一棵树为止,这棵树即哈夫曼树。
/** 用来保存左子树结点 */
HaffNode left[HALF_MAX];
/** 用来保存右子树结点 */
HaffNode right[HALF_MAX];
/** 冒泡排序:以权值大小降序 */
void SortHaffman(HaffNode* node, int length)
{
HaffNode tempNode;
for (int i = 0; i < length - 1; i++)
{
for (int j = 0; j < length - i - 1; j++)
{
//权值小的结点后移
if (node[j].weight < node[j + 1].weight)
{
tempNode = node[j];
node[j] = node[j + 1];
node[j + 1] = tempNode;
}
}
}
}
/**
* 构造赫夫曼树
* node 赫夫曼树的结点数组
* length 结点数组的长度
*/
void CreateHaffman(HaffNode* node, int length)
{
if (length <= 1)
return; //数组长度为1时结束递归
SortHaffman(node, length); //将结点数组按weight(权)从大到小排列
//构建一个以末尾两个结点为左右子结点的父节点,该父节点的权值为两个子结点权值之和
HaffNode parent;
//因为排过序了,所以最后一个结点[length-1]的权值肯定最小
left[length] = node[length - 1]; //权值第二小的结点在左
right[length] = node[length - 2]; //权值最小的结点在右
parent.weight = left[length].weight + right[length].weight; //父节点权值为左右结点权值之和
parent.leftChild = &left[length]; //左子树
parent.rightChild = &right[length]; //右子树
//将倒数第二位替换为该父节点,长度-1,递归创建赫夫曼树
node[length - 2] = parent;
CreateHaffman(node, length - 1);
}
编码过程
分解接收的字符串:遇“0”向左,遇“1”向右;一旦到达叶子结点,则译出一个字符,反复由根触发,直到译码完成。
特点:每一码都不是另一码的前缀,绝对不会错译
/**
* 编码过程(压缩过程)
* @param node 结点数组
* @param keepCode 返回存储编码后的字符数组
* @param index 当前操作的字符数组下标
*/
void Coding(HaffNode* node, char* keepCode, int index) {
if (!node) return;
//处理叶结点
if (node->leftChild == NULL || node->rightChild == NULL) {
//给编码数组设置一个终止符,形成一个完整的字符串,方便拷贝(防止拷贝到之前的编码)
keepCode[index] = '\0';
//node->data-0是char型转int型的简便方法
strcpy(code[node->data - 0], keepCode);
return;
}
//左分支编码为'0', 右分支编码为'1'
keepCode[index] = '0';
Coding(node->leftChild, keepCode, index + 1);
keepCode[index] = '1';
Coding(node->rightChild, keepCode, index + 1);
}