1. 程式人生 > >樹:哈夫曼樹的建立與編碼解碼

樹:哈夫曼樹的建立與編碼解碼

哈夫曼樹

哈夫曼樹即最優二叉樹,演算法如下:
(1)在當前未使用結點中找出兩個權重最小的作為左右孩子,計算出新的根結點
(2)新的根結點繼續參與過程(1),直至所有結點連線為一棵樹

如下圖,symbol為具體字元,Frequency為出現頻率(權重)
頻率表

Huffman樹構建過程

特點:只有度數為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