哈夫曼树的概念以及算法简述
1.相关名次的概念:
路径和路径长度:从树中的一个节点到达另外一个节点的之间的分支为路径,其长度为路径长度。树的路径长度定义为从根节点开始到达每一个节点的路径长度之和。
权值:节点的权值为该节点在所有节点中占的比重,拿一个文本文件来说,某个字符的节点权重即为该字符出现的次数与该文本文件大小的比值。
带权路径长度:树中某个节点的带权路径长度为该节点的路径长度与该节点的权重的比值。一颗树的带权路径长度(WPL)为该树中的所有叶节点的带权路径长度之和。
带权路径长度最小的树二叉树即为哈夫曼树(最优二叉树)!
算法描述:
1.根据给定的n个权值,构成n棵二叉树的集合:F = {T1,T2,T3,T4,…}。其中每棵二叉树中只有一个带权值为Wi的根节点,其左右孩子都为空。
2,在F中选取两棵权值最小的树,作为一个新节点的左右子树,构造出一课新的二叉树,且置新的树的权值为两棵左右子树的权值之和。
3,在F中删除这两棵左右子树,并将新的二叉树加入F中。
4,重复2,3过程,直到F只含有一棵二叉树。(这里即为树中根节点)
哈夫曼树的抽象数据类型的定义:
/* 哈夫曼树节点 */
typedef struct _haffman {
char data; //用来存放节点字符的数据域
int weight; //权重
struct _haffman *leftChild; //左孩子节点
struct _haffman *rightChild; //右孩子节点
}HaffNode;
宏定义
#define MAX_SIZE 256 //编码数量
#define HALF_MAX 128 //一半的数量
#define ASCII_SIZE 128 //ASCII码的数量
定义一些需要用到的全局变量
/* 以顺序结构存储的树节点--编码解码的字符映射表 --即存储原数据*/
HaffNode node[MAX_SIZE];
/* 用来保存所有左孩子节点--为总节点数的一半 */
HaffNode left[HALF_MAX];
/* 用来保存所有右孩子节点 --为总节点数的一半*/
HaffNode right[HALF_MAX];
/** 创建一个二维数组,用来保存每一个叶节点的编码 */
char code[MAX_SIZE][HALF_MAX];
构造哈夫曼树的代码实现:
/* 构造哈夫曼树
@param node 哈夫曼树的根节点
@param length 节点数组的长度
*/
void CreatHaffmanTree(HaffNode * node, int length)
{
if (length <= 0)
{
printf("长度为0,无法创建哈夫曼树!\n");
return;
}
SortHaffmanNode(node, length); //先进行节点权值的排序
HaffNode parent; //构建一个以node数组最后两个节点组成的父节点
left[length - 1] = node[length - 1]; //权重最小的节点
right[length - 1] = node[length - 2]; //权重第二小的节点
parent.weight = left[length - 1].weight + right[length - 1].weight; //累加权重
parent.leftChild = &left[length - 1]; //左孩子指向相对小的值
parent.rightChild = &right[length - 1]; //右孩子指向相对更大的值
node[length - 2] = parent; //将倒数第二个替代为parent节点,数组长度 - 1,递归创建哈夫曼树
CreatHaffmanTree(node, length - 1); //递归,并且长度自动减一,每一次都会进行一次重新排序。
}
实现构造哈夫曼树前需要先将节点数组的权重进行排序,即为上述SorHaffmanNode();函数,这里我使用的是冒泡排序的方法进行排序。
以下是其代码实现:
/* 哈夫曼树的排序 --使用冒泡排序
*@param node 节点数组
* @param length 节点的数量
*/
void SortHaffmanNode(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;
}
}
}
}
哈夫曼树的编码:
过程:从根节点出发,左支的编码为0,右支的编码为1.直到叶节点结束,其编码即为该节点数据的编码。每一次遍历都重新从根节点出发重新遍历。
代码实现:
/**
* 创建一个编码函数
* @param node 哈夫曼树节点数组
* @param tempnode 编码后的字符数组
* @param index 当前操作节点数组的下标
*/
void Coding(HaffNode * node, char * tempnode, int index)
{
if (!node) return;
if (node->leftChild == NULL || node->rightChild == NULL)
{
//使用字符数组,将编码形式保存
tempnode[index] = '\0'; //字符串结束的标志
strcpy(code[node->data - 0], tempnode); //这里叶节点的值使用的是字母,可以使用ASCII码的形式确认存储的位置,也可以用强制类型转换
}
tempnode[index] = '0'; //左支路的编码为0
Coding(node->leftChild, tempnode, index + 1); //先递归调用左支路,
tempnode[index] = '1'; //右支路的编码为1
Coding(node->rightChild, tempnode, index + 1); //再递归调用右支路
}
哈夫曼树的解码过程:
过程:对于一段由编码0/1构成的二进制文件,在同个树而言,重第一个开始,遇到0则访问左子树,遇到1则访问右子树。直到该节点没有了左右节点为止。
代码实现
/** 解码过程 -- flag 0/1 来表示往哪边走,左走0,右走1 */
HaffNode *Unziped(HaffNode *node, int flag)
{
if (flag == 1)
return node->rightChild; //右子树
else if(flag == 0)
return node->leftChild; //左子树
return NULL;
}
以上即是哈夫曼树通过C语言实现的代码了,从构造到编码到解码这三个过程。这是我看网上的教程上学的,但是在真正实现编码和解码过程中,解压缩后的文件和原文件多了一些没有必要的字符。现在还不知道问题出现在哪里,可能构建的时候出错了,望大佬指点哈哈!
如果有人需要全部代码实现也可以私信我哈,小白,不知道怎么将文件夹放上来,求知!