哈夫曼编码
一、哈夫曼编码
哈夫曼编码是无损压缩当中最好的方法。它使用预先二进制描述来替换每个符号,长度由特殊符号出现的频率决定。常见的符号需要很少的位来表示,而不常见的符号需要很多为来表示。
哈夫曼算法在改变任何符号二进制编码引起少量密集表现方面是最佳的。然而,它并不处理符号的顺序和重复或序号的序列。
二、原理
基本的原理是为每个符号找到新的二进制表示,从而通常符号使用很少的位,不常见的符号使用较多的位。
简短的说,这个问题的解决方案是为了查找每个符号的通用程度,我们建立一个未压缩数据的柱状图;通过递归拆分这个柱状图为两部分来创建一个二叉树,每个递归的一半应该和另一半具有同样的权(权是∑NK =1符号数k, N是分之中符号的数量,符号数k是符号k出现的次数)
这棵树有两个目的:
1. 编码器使用这棵树来找到每个符号最优的表示方法
2. 解码器使用这棵树唯一的标识在压缩流中每个编码的开始和结束,其通过在读压缩数据位的时候自顶向底的遍历树,选择基于数据流中的每个独立位的分支,一旦一个到达叶子节点,解码器知道一个完整的编码已经读出来了。
三、构造哈夫曼编码
以下图为例解释一个字串如何一步步变为哈夫曼编码
第一步:
在给定的字符串基础上统计各个字符出现的次数,这个不用说,会数数的都知道。
第二步:
构造哈夫曼树。在第一步的基础上,将出现次数从小到大排列,即成了E:1 B:3 A:5 F:6 C:7 G:9 D:12。
选择最小的两个,构造二叉树,生成一个父节点,父节点值为左右子树的和;在原序列中去除已经选走的两个节点,将新增的父节点加进去。
重复上述步骤,直到所有的节点都被添加进去了。
详细过程如下图所示:
第三步:
通过上述步骤,已经获得一个完整的哈夫曼树了,现在就可以自顶向下按路径编号,指向左节点的边编号0,指向右节点的边编号1,从根到叶节点的所有边上的0和1连接起来,就是叶子节点中字符的哈夫曼编码。
因此A:001 B:0001 C:111 D:10 E 0000 F:110 G:01
四、代码实现
这里提供一个获取哈夫曼编码的代码:(摘自http://blog.chinaunix.net/uid-26833883-id-3160434.html)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//哈夫曼树结点
typedef struct HuffNode
{
int weight;
char ch;
char code[20];
struct HuffNode *rchild;
struct HuffNode *lchild;
}HuffMan;
//队列设计
typedef struct _node_
{
HuffMan *data;
struct _node_ *next;
}ListNode;
typedef struct
{
ListNode *front;
ListNode *rear;
}Queue;
//create empty queue
Queue *create_empty_queue()
{
ListNode *HList;
Queue *Hqueue;
HList = (ListNode *)malloc(sizeof(ListNode));
HList->next = NULL;
Hqueue = (Queue *)malloc(sizeof(Queue));
Hqueue->front = Hqueue->rear = HList;
return Hqueue;
}
//入队
int EnterQueue(Queue *head,HuffMan *data)
{
ListNode *temp;
temp = (ListNode *)malloc(sizeof(ListNode));
temp->data = data;
temp->next = NULL;
head->rear->next = temp;
head->rear = temp;
return 0;
}
//有序插入结点
int OrderEnterQueue(Queue *head,HuffMan *p)
{
ListNode *m = head->front->next;
ListNode *n = head->front;
ListNode *temp;
while(m)
{
if(m->data->weight < p->weight)
{
m = m->next;
n = n->next;
}
else{
break;
}
}
//插到最后一个结点
if(m == NULL)
{
temp = (ListNode *)malloc(sizeof(ListNode));
temp->data = p;
temp->next = NULL;
n->next = temp;
head->rear = temp;
return 0;
}
//插入中间结点
temp = (ListNode *)malloc(sizeof(ListNode));
temp->data = p;
n->next = temp;
temp->next = m;
return 0;
}
//判断队列是否为空(注意,我们认为队列含有一个结点为空,想想为什么
//这样做?
int _is_empty_queue(Queue *head)
{
if(head->front->next->next == NULL)
{
printf("is_empty_queue\n");
return 1;
}
return 0;
}
//判断队列是否为空
int is_empty_queue(Queue *head)
{
if(head->front == head->rear)
return 1;
else
return 0;
}
//出队
HuffMan *DeleteQueue(Queue * head)
{
ListNode *temp;
temp = head->front;
head->front = temp->next;
free(temp);
temp = NULL;
return head->front->data;
}
//创建哈夫曼树
HuffMan *create_huffman_tree(Queue *head)
{
HuffMan *right,*left,*current;
//循环结束时,队列只含有一个结点
while(!_is_empty_queue(head))
{
left = DeleteQueue(head);
right = DeleteQueue(head);
current = (HuffMan *)malloc(sizeof(HuffMan));
current->weight = left->weight + right->weight;
current->rchild = right;
current->lchild = left;
OrderEnterQueue(head,current);
}
return head->front->next->data;
}
//哈夫曼编码
int HuffmanCode(HuffMan *root)
{
HuffMan *current = NULL;
Queue *queue = NULL;
queue = create_empty_queue();
EnterQueue(queue, root);
while(!is_empty_queue(queue))
{
current = DeleteQueue(queue);
if(current->rchild == NULL && current->lchild == NULL)
{
printf("%c:%d %s\n",current->ch,current->weight,current->code);
}
if(current->lchild)
{
strcpy(current->lchild->code,current->code);
strcat(current->lchild->code,"0");
EnterQueue(queue, current->lchild);
}
if(current->rchild)
{
strcpy(current->rchild->code,current->code);
strcat(current->rchild->code,"1");
EnterQueue(queue, current->rchild);
}
}
return 0;
}