目录
Huffuman树的概念和应用
概念
在树中,
一个结点到达另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目是路径长度。
从树的根节点到达每一个结点的路径长度之和叫做树的路径长度。
从树的根结点到某叶子结点之间的路径长度和该结点上权的乘积称为该结点的带权路径长度。
带权路径长度(WPL)最小的二叉树是哈夫曼树。
若要设计长短不同的编码,则必须是任意字符的编码不是另一个字符编码的前缀,这种编码称为前缀编码。
应用
1.在成绩不同区间时,用ifelse判断时,大部分成绩位于七十到九十之间,而if语句大多从60或者90开始判断,效率不高,哈夫曼树的构造有效对这一问题进行改进。但是哈夫曼树构造相对复杂,主要目的不在于转换成绩。
2.在当时,哈夫曼树主要用于解决远距离通信的数据传输的优化问题,也可以理解为压缩和解压缩。规定左分支代表0右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是哈夫曼编码。
哈夫曼树的构造算法
哈夫曼树其实是属于贪心算法。也就是在问题求解时,总是做出当前看来最好的选择,也就是说,不从整体最优加以考虑,而只是得到某种意义的局部最优解,但是此时的局部最优解恰好等于全局最优解。
哈夫曼树的构造算法也就是找到每次权值最小的子树构造新的二叉树,且该二叉树的结点为其左右结点根的权值之和。
typedef struct {
char data;//结点值
double weight;//权值
int parent;
int lchild;
int rchild;
} HmTNode;
//n个叶子结点,2*n-1个结点
void CreatHmT(HmTNode hm[], int n) {
//先将所有结点的初始值设置为-1
for (int i = 0; i < 2 * n - 1; i++)
hm[i].parent = hm[i].lchild = hm[i].rchild = -1;
int min1, min2; //存储局部最优解
int lnode, rlode; //存储左右结点
//哈夫曼树的0~n-1存的是叶子结点,从第n个开始构造
for (int i = n; i < 2 * n - 1; i++) {
min1 = 32767, min2 = 32767;
lnode = -1, rlode = -1;
//在前n个未构造的数值中寻找最小的两个构造
for (int k = 0; k < n; k++) {
if (hm[k].parent == -1) {
if (hm[k].weight < min1) {
min2 = min1;//保存上一个最小的信息
rlode = lnode;
min1 = hm[k].weight;//保留这次最小的信息
lnode = k;
} else if (hm[k].weight < min2) {
min2 = hm[k].weight;
rlode = k;
}
}
}
//找到两个最小的结点之后,构造子树
hm[i].weight = hm[lnode].weight + hm[rlode].weight;
hm[i].lchild = lnode, hm[i].rchild = rlode;
hm[lnode].parent = i, hm[rlode].parent = i;
}
}
哈夫曼编码
哈夫曼编码的实质是使用频率越高的字符采用更简短的编码。
//哈夫曼编码
const int N = 1010;
typedef struct {
char cd[N];//存放当前结点的哈夫曼编码
int start;//
} HCode;
void CreateHCode(HmTNode hm[], HCode hcd[], int n) {
//根据哈夫曼树求哈夫曼编码
HCode hc;
int c, f;
for (int i = 0; i < n; i++) {
hc.start = n;
c = i;
f = hm[i].parent;
//code
while (f != -1) { //左子树0右子树1
if (hm[f].lchild == c)
hc.cd[hc.start--] = '0';
else
hc.cd[hc.start--] = '1';
c = f;
f = hm[f].parent; //继续循环
}
hc.start++;
hcd[i] = hc; //从start开始到n结束
}
}
参考资料:
《大话数据结构》程杰
《数据结构教程》李春葆