数据结构学习记录DAY13 :哈夫曼树(上)
哈(赫)夫曼树和哈(赫)夫曼编码
- 路径 一个结点到另外一个结点的通路,称为路径 (祖先结点到子孙结点)
- 路径长度: 每经过一个结点,路径长度就增加1,不包括起始结点的
- 结点权值:对于结点赋予一个数值,表示结点的权值 比较:结点元素出现的次数
- 带权路径长度: 从根点出发到该结点的路径长度 乘以 该结点的权值
- 树的带权路径长度 (WPL): 树中所有叶子结点的带权路径长度之和
- 哈夫曼树:最优二叉树,由n个叶结点组成的二叉树的带权路径长度最短
- 结点相同,构成的哈夫曼树可能不唯一,但树的带权路径长度相等
- 把n个结点构成哈夫曼树,这n个结点必然作为叶子结点,需要添加n-1个分支结点
构建哈夫曼树
- 遵循的原则:权重越大离根结点越近
- 算法描述过程
-
- 把n个叶结点看作n棵独立的树,构成森林F
-
- 创建一个新的结点,然后从森林F中选取两棵根结点权值最小的树作为新结点的左右子树,并且把新的结点的权值设置为这两棵树根结点权值之和
-
- 从森林F中把刚才选取两棵树删除,并且把新的结点作为树的根结点加入到森林中
-
- 重复2和3的步骤,直到森林中只剩下一棵树为止
-
A:3 B:4 C:1 D:5 E:10 F:6 G:2
E:10 F:6 D:5 B:4 A:3 G:2 C:1
3
/ \
G C
E:10 F:6 D:5 B:4 A:3 GC:3
6
/ \
A 3
/ \
G C
E:10 F:6 A GC:6 D:5 B:4
9
/ \
D B
Fx:12 E:10 DB:9
12
/ \
F 6
/ \
A 3
/ \
G C
哈夫曼树:
31
/ \
19 12
/ \ / \
E 9 F 6
/ \ / \
D B A 3
/ \
G C
A:3 B:4 C:1 D:5 E:10 F:6 G:2
E:00 D:010 B:011 F:10 A:110 G:1110 C:1111
3x3 + 4x3 + 1x4 + 5x3 + 10x2 + 6x2 + 2x4
= 9 + 12 + 4 + 15 + 20 + 12 + 8
哈夫曼树特性
- 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大
- 哈夫曼树的结点总数为2n − 1
- 哈夫曼树中不存在度为1的结点
- 哈夫曼树并不唯一,但WPL必然相同且为最优
哈夫曼编码
- 编码
- 定义编码
- ASCII 定长编码 每个字符都是8个二进制位
- A 65 0100 0001
- B 66 0100 0010
- C 67 0100 0011
- D 68 0100 0100
- unicode 万国码 4byte
- 变长编码 utt-8 中文汉字2-4byte gbk 2byte
- 哈夫曼编码就是变长编码
- 哈夫曼编码是前缀编码 任意一个字符的编码都不会是另外一个字符编码的前缀
- 前缀编码不会有歧义 非前缀编码会产生歧义
100道选择题: 按照ascii发送: 传输100byte
A: 00 B: 01 C: 10 D: 11 100*2bit/8 byte = 25byte
A:50 B:20 C:15 D:15
A:0 B:10 C:110 D:111 50x1+20x2+30x3bit/8 = 180/8 = 22.5byte
A:0 B:00 C:010 D:011 非前缀编码
-
由哈夫曼树获取哈夫曼编码
-
从根结点出发,左子树为0,右子树为1,到所有叶子结点所经过的路径就构成了哈夫曼编码
有了以上的知识点就可以准备着手创建哈夫曼树和生成哈夫曼编码了
多文件编程如下:
main.c#include "Huffman.h" void printEWC(unsigned char elem, size_t weight, char *code) { printf("%c %u %s\n",elem, weight, code); } int main() { unsigned char elm[] = "ABCDEFGHIJ"; size_t weight[] = {5,7,4,3,1,2,6,9,0,8}; HUFF tree = create_HuffmanTree(elm, weight, 10); get_HuffCode(tree); foreach_HuffmanTree(tree, printEWC); destroy_HuffmanTree(tree); }
-
Huffman.h
#ifndef _HUFFMAN_H_
#define _HUFFMAN_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#define SUCCESS 0
#define FAILURE -1
#define CODE_LEN 256
typedef unsigned char ElemType;
struct HuffTree{
ElemType data;//
size_t weight;//权重
char code[CODE_LEN];//存储时转化为而精致
struct HuffTree *lchild;
struct HuffTree *rchild;
};
typedef struct HuffTree *HUFF;
HUFF create_HuffmanTree(ElemType elems[], size_t weight[], size_t n);//elems为元素, weight为权重
//获得哈夫曼编码
void get_HuffCode(HUFF tree);
//输出哈夫曼编码
void foreach_HuffmanTree(HUFF tree, void (*visit)(ElemType, size_t, char*));
//
void destroy_HuffmanTree(HUFF tree);
#endif// _HUFFMAN_H_
Huffman.c
#include "Huffman.h"
int compareWeight(const void *v1, const void *v2)
{
HUFF *h1 = (HUFF*) v1;
HUFF *h2 = (HUFF*) v2;
//return ((*h2)->weight - (*h1)->weight);
if((*h1)->weight < (*h2)->weight){
return 1;
}
else if((*h2)->weight < (*h1)->weight){
return -1;
}
else{
return 0;
}
}
//elems为元素, weight为权重
HUFF create_HuffmanTree(ElemType elems[], size_t weight[], size_t n)
{
//HUFF *tree = (HUFF*)malloc(sizeof(HUFF*) * n);
HUFF tree[n];
if(NULL == tree){
return NULL;
}
for(int i = 0; i < n; ++i){
tree[i] = (HUFF)malloc(sizeof(struct HuffTree));
tree[i]->data = elems[i];
tree[i]->weight = weight[i];
tree[i]->lchild = tree[i]->rchild = NULL;
memset(tree[i]->code, 0, CODE_LEN);
}
qsort(tree, n, sizeof(HUFF), compareWeight);
for(int i = 0; i < n; ++i){
//此时i为最后一个结点
printf("%c(%u)", tree[i]->data,tree[i]->weight);
}
printf("\n");
HUFF root = NULL;
for(int i = n-1; i > 0; i--){
root = (HUFF)malloc(sizeof(struct HuffTree));
root->lchild = tree[i-1];
root->rchild = tree[i];
root->weight = tree[i-1]->weight + tree[i]->weight;
//memset(root->code, 0, CODE_LEN);
int j;
for(j = i - 2; j >= 0 && tree[j]->weight < root->weight; --j){
tree[j+1] = tree[j];
}
tree[j + 1] = root;
}
root = tree[0];
return root;
}
//获得哈夫曼编码
void get_HuffCode(HUFF tree)
{
if(NULL != tree){
if(tree->lchild != NULL){
strcpy(tree->lchild->code,tree->code);
strcat(tree->lchild->code, "0");
get_HuffCode(tree->lchild);
}
if(tree->rchild != NULL){
strcpy(tree->rchild->code,tree->code);
strcat(tree->rchild->code, "1");
get_HuffCode(tree->rchild);
}
}
}
//输出哈夫曼编码
void foreach_HuffmanTree(HUFF tree, void (*visit)(ElemType, size_t, char*))
{
if(tree != NULL){
if(tree->lchild == NULL && NULL == tree->rchild){
visit(tree->data, tree->weight, tree->code);
}
else{
foreach_HuffmanTree(tree->lchild, visit);
foreach_HuffmanTree(tree->rchild, visit);
}
}
}
//
void destroy_HuffmanTree(HUFF tree)
{
if(tree != NULL){
destroy_HuffmanTree(tree->lchild);
destroy_HuffmanTree(tree->rchild);
free(tree);
}
}
文件的压缩和解压
- 利用哈夫曼编码对文件进行压缩和解压
- 算法过程
- 读取文件中的内容,统计每一个字符出现的次数
- 根据得到的字符-对应的次数构建哈夫曼树,得到哈夫曼编码
- 对文件中的字符用哈夫曼编码进行压缩
- 为了能够解压,需要把哈夫曼树存储的到文件中
以上
希望有帮到你