哈弗曼树与哈弗曼编码
一、前置知识
什么是编码
‘a’ = 97 = ( 0110 0001 ) 2 (0110\ 0001)_2 (0110 0001)2
‘0’ = 48 = ( 0011 0000 ) 2 (0011\ 0000)_2 (0011 0000)2
注意:任何信息,在计算机中,都是二进制存储的
信息:“ a a 00 aa00 aa00” = 01100001 、 01100001 、 00110000 、 00110000 01100001、01100001、00110000、00110000 01100001、01100001、00110000、00110000
一台计算机 传输到 另外一台计算机,传输 32 个比特位
假设:计算机的网络是 32 b i t / s 32bit/s 32bit/s。所以用时: 1 s 1 \ s 1 s
特定场景:只有 a , b , 0 , 1 a,b,0,1 a,b,0,1 四种字符需要传输
自制My_X
编码:
a
:
00
,
b
:
01
,
0
:
10
,
1
:
11
a:00, \ \ b: 01,\ \ 0: 10,\ \ 1: 11
a:00, b:01, 0:10, 1:11
“ a a 00 aa00 aa00” = 00001010 00001010 00001010
在带宽不变的情况下,当前只需要传输 0.25 s 0.25\ s 0.25 s
定长与变长编码
-
A
S
C
I
I
ASCII
ASCII 编码 和 特定场景下的
My_X
编码,都属于定长编码 - 对于每一个字符,编码长度相同,这就是定长编码
- 【大家自行补充】 U T F − 8 UTF-8 UTF−8编码,是变长编码, U T F − 16 UTF-16 UTF−16,是定长编码
- 对于每一个字符,编码长度不相同,这就是变长编码
- 将定长编码,看成是变长编码的特例
- 变长编码,一定不差于定长编码
变长编码应用场景
特定场景:
-
只有四种字符 : a 、 b 、 0 、 1 a、b、0、1 a、b、0、1
-
出现概率: a : 0.8 , b : 0.05 , 0 : 0.1 , 1 : 0.05 a: 0.8,\ \ b: 0.05, \ \ 0: 0.1,\ \ 1: 0.05 a:0.8, b:0.05, 0:0.1, 1:0.05
-
平均编码长度: a v g ( l e n ) = ∑ l e n i × p i avg(len) = \sum{len_i}\times{p_i} avg(len)=∑leni×pi
l e n i len_i leni:第
i
种字符,编码长度p i p_i pi:第
i
种字符,出现概率
My_X
编码的平均编码长度:
a
v
g
(
l
)
=
2
×
∑
p
i
=
2
avg(l) = 2\times\sum{p_i}=2
avg(l)=2×∑pi=2
(My_X自己设计的定长编码)
平均编码长度:2,假设传输100个字符,则估算需传输200个比特位
新设计 My_Y
编码:
a
:
1
、
b
:
01
、
0
:
000
、
1
:
001
a: 1、\ \ b: 01、\ \ 0: 000、\ \ 1: 001
a:1、 b:01、 0:000、 1:001
平均编码长度: 1 ∗ 0.8 + 2 ∗ 0.05 + 3 ∗ 0.1 + 3 ∗ 0.05 = 1.35 1*0.8+2*0.05+3*0.1+3*0.05=1.35 1∗0.8+2∗0.05+3∗0.1+3∗0.05=1.35
(My_Y自己设计的变长编码)
平均编码长度:1.35,假设传输100个字符,则估算需传输135个比特位
二、哈弗曼编码
- 首先,统计得到每一种字符的概率
- 将
n
个字符,建立成一棵哈弗曼树 - 每一个字符,都落在叶子结点上。且此字符的出现频率也可叫做结点的权
- 按照
左0
,右1
的形式,将编码读取出来
具体来说,依旧用只有四种字符 : a 、 b 、 0 、 1 a、b、0、1 a、b、0、1,且出现概率为 a : 0.8 , b : 0.05 , 0 : 0.1 , 1 : 0.05 a: 0.8,\ \ b: 0.05,\ \ 0: 0.1,\ \ 1: 0.05 a:0.8, b:0.05, 0:0.1, 1:0.05来举例说明
-
在
n
个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和; -
在原有的
n
个权值中删除那两个最小的权值,同时将新的权值加入到n–2
个权值的行列中,以此类推; -
重复
1
和2
,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
得到新编码:
a : 0 ∣ b : 110 ∣ 0 : 10 ∣ 1 : 111 a: 0 | b: 110 | 0: 10 | 1: 111 a:0∣b:110∣0:10∣1:111
平均编码长度: 1 ∗ 0.8 + 3 ∗ 0.05 + 2 ∗ 0.1 + 3 ∗ 0.05 = 1.3 1*0.8+3*0.05+2*0.1+3*0.05=1.3 1∗0.8+3∗0.05+2∗0.1+3∗0.05=1.3
哈夫曼编码(变长)
平均编码长度:1.3,假设传输100个字符,则估算需传输130个比特位
结论:哈弗曼编码,是最优的变长编码
/*************************************************************************
> File Name: Huffman_tree.c
> Author: Luzelin
> Mail:
> Created Time:
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#define swap(a, b) { \
__typeof(a) __temp = a; \
a = b; b = __temp; \
}
typedef struct Node {
char key; //字符
double rate; //出现概率
struct Node *lchild, *rchild;
} Node;
Node* GetNodeNode(char c, double val) {
Node *s = (Node *)malloc(sizeof(Node));
s->key = c;
s->rate = val;
s->lchild = s->rchild = NULL;
return s;
}
//将data中最小的rate结点,放在n位置
void pick_min(Node **data, int n) {
for (int i = n - 1; i >= 0; --i) {
if (data[n]->rate > data[i]->rate) {
swap(data[n], data[i]);
}
}
return ;
}
//合并两个结点,并返回它们的父结点
Node* ComBinNode(Node *a, Node *b) {
Node *s = (Node *)malloc(sizeof(Node));
s->key = 0;
s->rate = a->rate + b->rate;
s->lchild = a;
s->rchild = b;
return s;
}
//将data指向的指针数组中的所有结点合并成一颗哈夫曼树
Node* GetHuffmanTree(Node **data, int n) {
if (n == 0) return NULL;
//总共合并n-1次,n为结点数
for (int i = 1; i < n; ++i) {
//每次合并出现概率最小和次小的结点,并将其父结点存放至次小位置
pick_min(data, n - i);
pick_min(data, n - i - 1);
data[n - i - 1] = ComBinNode(data[n - i], data[n - i - 1]);
}
//最终data[0]就是整棵Huffman树的根结点
return data[0];
}
void __output(Node *root, char *str, int k) {
str[k] = 0;
if (root->lchild == NULL && root->rchild == NULL) {
printf("%c : %f : %s\n", root->key, root->rate, str);
return ;
}
str[k] = '0';
__output(root->lchild, str, k + 1);
str[k] = '1';
__output(root->rchild, str, k + 1);
return ;
}
void output(Node *root) {
if (root == NULL) return ;
char str[100] = {0};
__output(root, str, 0);
return ;
}
void clear(Node *root) {
if (root == NULL) return ;
clear(root->lchild);
clear(root->rchild);
free(root);
return ;
}
int main() {
int n;
scanf("%d", &n);
Node **data = (Node **)malloc(sizeof(Node *) * n);
for (int i = 0; i < n; ++i) {
char str[5];
double val;
scanf("%s%lf", str, &val);
data[i] = GetNodeNode(str[0], val);
}
Node *root = GetHuffmanTree(data, n);
output(root);
clear(root);
free(data);
return 0;
}
input
26
A 0.034748
B 0.079101
C 0.001086
D 0.082359
E 0.060224
F 0.066238
G 0.047528
H 0.028817
I 0.006098
J 0.021216
K 0.010107
L 0.002339
M 0.042349
N 0.046525
O 0.004928
P 0.054962
Q 0.066154
R 0.062563
S 0.054043
T 0.049282
U 0.007601
V 0.014033
W 0.040428
X 0.071751
Y 0.019713
Z 0.025810
output:
N 0000
J 00010
Z 00011
G 0010
T 0011
S 0100
P 0101
I 0110000
U 0110001
V 011001
H 01101
E 0111
R 1000
Q 1001
F 1010
X 1011
A 11000
C 110010000
L 110010001
O 11001001
K 1100101
Y 110011
B 1101
D 1110
W 11110
M 11111