// 哈夫曼编码译码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CHAR 128
#define MAXN 256
// 哈夫曼树结点定义
typedef struct {
char ch; //叶结点存储字符
int weight; //字符出现频次
int parent, lchild, rchild;
} HTNode, HuffmanTree[MAXN];
// char*:字符串指针,存放一个字符对应的哈夫曼编码("0101")
// HuffCode:数组类型,每个元素都是char*
typedef char* HuffCode[MAX_CHAR];
//==============================================
// 小根堆定义(存放哈夫曼树结点下标,按weight比较)
typedef struct{
int elem[MAXN]; //存放哈夫曼树结点下标
int len; //堆的大小
} HeapNode,*Heap;
// 向下调整:p位置元素下沉(按weight比较)
void SiftDown(Heap H, int p, int n, HuffmanTree HT){
int e = H->elem[p];
for(int i = 2*p; i <= n; i *= 2){
if(i < n && HT[H->elem[i+1]].weight < HT[H->elem[i]].weight) i++;
if(HT[e].weight <= HT[H->elem[i]].weight) break;
H->elem[p] = H->elem[i];
p = i; //p位置下沉
}
H->elem[p] = e; //堆中存放哈夫曼树结点下标
}
// 堆插入:堆尾插入元素,向上调整(按weight比较)
void HeapInsert(Heap H, int e, HuffmanTree HT){
int i = ++H->len;
for( ; i > 1; i /= 2){
if(HT[H->elem[i/2]].weight <= HT[e].weight) break;
H->elem[i] = H->elem[i/2];
}
H->elem[i] = e; //堆中存放哈夫曼树结点下标
}
// 堆删除:用堆尾替换堆顶,向下调整,返回堆顶元素
int HeapDelete(Heap H, HuffmanTree HT){
if(H->len <= 0) return -1;
int e = H->elem[1];
H->elem[1] = H->elem[H->len]; //替换
SiftDown(H, 1, --H->len, HT);
return e;
}
//==============================================
// 构造哈夫曼树:用小根堆存取哈夫曼树的结点下标
// n:叶子数目,w[]:字符频次,chars[]:字符
void CreateHuffTree(HuffmanTree HT, int n, int w[], char chars[]){
if(n <= 1) return;
int m = 2 * n - 1; //总结点数
Heap heap=(HeapNode*)malloc(sizeof(HeapNode));
// 初始化全部结点
for(int i = 1; i <= m; i++){
HT[i].weight = 0;
HT[i].ch = '\0';
HT[i].parent = HT[i].lchild = HT[i].rchild = 0;
}
// 叶子结点赋值
for(int i = 1; i <= n; i++){
HT[i].ch = chars[i-1];
HT[i].weight = w[i-1];
HeapInsert(heap, i, HT); //哈夫曼树结点下标i插入堆
}
// 循环合并两个最小权树
for(int i = n+1; i <= m; i++){
int s1 = HeapDelete(heap, HT); //取出堆顶weight最小的哈夫曼树结点下标
int s2 = HeapDelete(heap, HT);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight; //合并权值
HeapInsert(heap, i, HT); //哈夫曼树结点下标i插入堆
}
free(heap);
}
// 生成哈夫曼编码(叶子向上回溯)
void CreateHuffCode(HuffmanTree HT, HuffCode HC, int n){
char *cd = (char*)malloc(n*sizeof(char));
cd[n-1] = '\0';
int c,f,start;
for(int i = 1; i <= n; i++){
start = n-1;
c = i;
f = HT[c].parent;
while(f != 0){
if(HT[f].lchild == c)
cd[--start] = '0';
else
cd[--start] = '1';
c = f;
f = HT[c].parent;
}
unsigned char ch = HT[i].ch;
HC[ch] = (char*)malloc((n - start) * sizeof(char));
strcpy(HC[ch], cd + start); //复制字符串
}
free(cd);
}
// 字符串编码:输入原串,输出01比特串
void EncodeString(HuffCode HC, char *src, char *codeStr){
codeStr[0] = '\0';
for(int i = 0; src[i] != '\0'; i++){
strcat(codeStr, HC[(unsigned char)src[i]]); //追加字符串
}
}
// 哈夫曼译码:01串还原原始字符串
int DecodeString(HuffmanTree HT, int n, char *codeStr, char *resStr){
int root = 2*n-1; //根节点下标
int p = root;
int idx = 0;
for(int i=0; codeStr[i]!='\0'; i++){
if(codeStr[i] == '0')
p = HT[p].lchild;
else if(codeStr[i] == '1')
p = HT[p].rchild;
else
return -1; //非法字符
// 叶子结点,读取字符
if(HT[p].lchild == 0 && HT[p].rchild ==0){
resStr[idx++] = HT[p].ch;
p = root; // 返回根,继续译码下一个字符
}
}
resStr[idx] = '\0';
if(p != root) return -2; //编码串不完整
return 1;
}
int main(){
char inputStr[] = "DADADCF"; //ASCII码字符
printf("原始字符串:%s\n",inputStr);
//统计字符频次
int freq[MAX_CHAR]={0};
for(int i=0;inputStr[i]!='\0';i++){
freq[(unsigned char)inputStr[i]]++;
}
//收集出现过的字符
char chars[100];
int w[100], n=0;
for(int i=0;i<MAX_CHAR;i++){ //枚举128个字符
if(freq[i]>0){
chars[n] = (char)i; //字符
w[n] = freq[i]; //字符出现频次
n++; //字符数
}
}
//构造哈夫曼树,生成哈夫曼编码
HuffmanTree HT;
HuffCode HC={NULL};
CreateHuffTree(HT,n,w,chars); //构造哈夫曼树
CreateHuffCode(HT,HC,n); //生成哈夫曼编码
printf("\n====字符哈夫曼编码表====\n");
for(int i=0;i<n;i++){
unsigned char c = chars[i];
printf("字符 %c 频次:%d 编码:%s\n", c, freq[c], HC[c]);
} //例 A:HC[65]="10", C:HC[67]="110"
//编码原字符串为01串
char codeStr[1024]={0};
EncodeString(HC,inputStr,codeStr);
printf("\n字符串编码(01串): %s\n",codeStr);
//译码,还原字符串
char decodeStr[1024]={0};
int ret = DecodeString(HT,n,codeStr,decodeStr);
if(ret == 1)
printf("\n译码还原字符串:%s\n",decodeStr);
else
printf("\n译码失败\n");
return 0;
}