樹:哈夫曼樹的建立與編碼解碼
阿新 • • 發佈:2018-11-26
哈夫曼樹
哈夫曼樹即最優二叉樹,演算法如下:
(1)在當前未使用結點中找出兩個權重最小的作為左右孩子,計算出新的根結點
(2)新的根結點繼續參與過程(1),直至所有結點連線為一棵樹
如下圖,symbol為具體字元,Frequency為出現頻率(權重)
特點:只有度數為0和2的結點
C語言靜態連結串列實現哈夫曼樹
實現功能:輸入一段英文文字,統計各字元出現次數作為權重,以當前字符集生成哈夫曼樹,給出所有字元及指定編碼與文字編碼,再將編碼後的文字解碼為原文
資料結構定義
下標 | 資料域 | 父結點下標 | 左子樹下標 | 右子樹下標 |
---|
typedef char ElementType;
typedef struct {
ElementTree data; 結點資料
int weight; 結點權重
int parent; 雙親下標
int left, right; 左右子樹下標
}HuffmanTree;
- 統計字元及初始化靜態連結串列
用下標從32 ~ 126的陣列記錄ASCII碼32 ~ 126的字元,包含了英文文字的絕大多數字符
int num = 1;當前靜態連結串列有效長度 [1,num)
char text[2005];//文字源
char *textAnalyze() {//返回字符集中字元個數的陣列
char *chars = (char *)malloc(sizeof(char)*127);//32~126
char c = 0;
memset(chars, 0, sizeof(char)*127);
scanf("%[^\n]", text);
int i = 0;
while (c = text[i++]) {
chars[c]++;//計數
}
for (i = 32; i <= 126; i++) {
if (chars[i]) {
printf("字元%c 出現%d次\n", i, chars[ i]);
}
}
return chars;
}
void initElement(HuffmanTree *nodes, char *chars) {
memset(nodes, 0, sizeof(char) * sizeof(HuffmanTree) * 200);
for (int i = 32; i <= 126; i++) {
if (chars[i]) {
nodes[num].data = i;
nodes[num].weight = chars[i];
nodes[num].parent = nodes[num].left = nodes[num].right = 0;
num++;//全域性變數記錄當前結點總數
}
}
free(chars);
}///////初始化靜態連結串列完畢
eg:
輸入為affgghhhjjj
此時靜態連結串列為
下標 | 資料域 | 權重 | 父結點下標 | 左子樹下標 | 右子樹下標 |
---|---|---|---|---|---|
1 | a | 1 | 0 | 0 | 0 |
2 | f | 2 | 0 | 0 | 0 |
3 | g | 2 | 0 | 0 | 0 |
4 | h | 3 | 0 | 0 | 0 |
5 | j | 3 | 0 | 0 | 0 |
建立哈夫曼樹
void createHuffmanTree(HuffmanTree *nodes) {
每次連線兩個結點,生成一個新結點,連線完成應該生成n-1個結點
故 n個結點建立的哈夫曼樹應當有2n-1個結點
int end = num + num - 3;//計算總結點數
int *min = NULL;
while (num != end+1) {
min = searchOrder(nodes);
//製作新結點
nodes[num].weight = nodes[min[0]].weight + nodes[min[1]].weight;
nodes[num].left = min[0];
nodes[num].right = min[1];
//填補原結點
nodes[min[0]].parent = num;
nodes[min[1]].parent = num;
num++;
free(min);
}
}
其中searchOrder( )返回當前權重最小值與次小值的下標
int *searchOrder(HuffmanTree *nodes) {// num>=2
int *nums = (int *)malloc(sizeof(int) * 2);
int i = 1;
for (; i < num&&nodes[i].parent != 0; i++);//nodes[i].parent == 0 可用
1*- nums[0] = i;// 0 pre 1 later
for (i++; i < num&&nodes[i].parent != 0; i++);
nums[1] = i;//找到初始兩下標
for (i++; i < num; i++) {
if (nodes[i].parent == 0) {//未使用
if (nodes[i].weight < nodes[nums[1]].weight) {// <min
nums[1] = i;
}
else if (nodes[i].weight < nodes[nums[0]].weight) {
nums[0]=nums[1],nums[1] = i;
}
}//按出現順序生成最優二叉樹
}
return nums;
}
此時的哈夫曼樹為
下標 | 資料域 | 權重 | 父結點下標 | 左子樹下標 | 右子樹下標 |
---|---|---|---|---|---|
1 | a | 1 | 6 | 0 | 0 |
2 | f | 2 | 6 | 0 | 0 |
3 | g | 2 | 7 | 0 | 0 |
4 | h | 3 | 7 | 0 | 0 |
5 | j | 3 | 8 | 0 | 0 |
6 | 3 | 8 | 1 | 2 | |
7 | 5 | 9 | 3 | 4 | |
8 | 6 | 9 | 5 | 6 | |
9 | 11 | 0 | 7 | 8 |
可以看出葉子結點左右孩子均為0,根結點父結點域為0
哈夫曼編碼
字首編碼:每個字元的編碼都不為其餘編碼的字首
非字首編碼:存在某字元的編碼是其餘某編碼的字首
(沒錯就是這麼扭曲)
所有參與編碼的字元都在葉子結點上,因此保證編碼為字首編碼
哈夫曼編碼:走左子樹為0,走右子樹為1。從樹根走到葉子結點組成的01序列
哈夫曼編碼是字首編碼
- 遍歷哈夫曼樹得到每個葉子結點的編碼
typedef struct {
ElementTree data;字元
char hfCode[115];該字元對應的編碼序列
}HfCode;
HfCode codes[111];儲存每個字元的哈夫曼編碼
int charNum = 0;//字符集中的字元個數 [0,num)
char encodedText[4005];//編碼後的文字
void encodeAll(HuffmanTree *nodes, int index, char *order, int cnt) {
if (nodes[index].left == nodes[index].right) {
printf("%c : ", nodes[index].data);
order[cnt] = 0;
puts(order);
codes[charNum].data = nodes[index].data;
strcpy(codes[charNum++].hfCode,order);
}
if (nodes[index].left) {
order[cnt] = '0';
encodeAll(nodes, nodes[index].left, order, cnt+1);
order[cnt] = '1';
encodeAll(nodes, nodes[index].right, order, cnt+1);
}
}
- 從葉子結點走到根得到該葉子的編碼
void getCodeByChar(HuffmanTree *nodes, char leaf) {//得到某個葉子節點的編碼
int index;
int end = num / 2;
for (index = 0; index <= end; index++) {
if (nodes[index].data == leaf)
break;
}
if (index > end) {
printf("輸入有誤!");
return;
}
char order[115];
int cnt = 0;
while (nodes[index].parent) {不為根
order[cnt++] = nodes[nodes[index].parent].left == index ? '0' : '1';
index = nodes[index].parent;
}
printf("%c : ", leaf);
for (cnt--; cnt >= 0; cnt--) {
printf("%c", order[cnt]);
}
printf("\n");
}
在建立哈夫曼樹並得到各字元編碼的基礎上對整個文字進行編碼/解碼就十分容易了
void encodeText(HuffmanTree *nodes) {//編碼
int i, j, len = strlen(text);
printf("該資訊為:\n%s\n", text);
printf("該資訊的哈夫曼編碼為:\n");
for (i = 0; i < len; i++) {
for (j = 0; j < charNum&&codes[j].data != text[i]; j++);
strcat(encodedText, codes[j].hfCode);
}
printf("%s\n",encodedText);
}
void decodeText(HuffmanTree *nodes, char *unknown) {//解碼
int len = strlen(unknown);
int root = num - 1;
int i, index;
for (i = 0, index = root; i < len; i++) {
index = unknown[i] == '0' ? nodes[index].left : nodes[index].right;
if (nodes[index].left == 0) {
printf("%c", nodes[index].data);
index = root;
}
}
}
解碼的主要思路是從哈夫曼樹的根開始,遍歷整個01序列,按照編碼的方式,0向左走,1向右走,走到葉子結點輸出,即譯出一個字元,迴圈變數重新回到根結點繼續解譯下一個字元。因為字首編碼的前提保證,不會有歧義。
2019/11/19