哈夫曼編碼


題目:要傳輸一則報文內容如下:
“AAAAAAAAAAAAAAABBBBBBBBBCCCCCCCCDDDDDDDDDDDDEEEEEEEEEEFFFFF”
請為這段報文設計哈夫曼編碼,要求如下:

  1. 請計算出每個字符出現的概率,並以概率為權重來構造哈夫曼樹,寫出構造過程、畫出最終的哈夫曼樹,得到每個字符的哈夫曼編碼。
  2. 請將上述設計哈夫曼編碼的過程,用代碼來實現,並輸出各個字母的哈夫曼編碼。(有代碼,有運行結果的截圖)
  3. 請分析算法的效率,至少包括時間復雜度和空間復雜度等。

一、構造過程
① 統計可知,字符串全長共59個字符,其中字符‘A’-‘F’的出現頻數及對應概率(保留兩位小數)如圖所示:

②將每個字符出現概率作為權重,則對於上述給定的6個權值{25,15,14,20,17,9},依次構造6棵只有根節點的二叉樹,共同構成森林F;

③在森林F中,選取權重最小和次小的兩顆樹,分別作為新二叉樹的左子樹、右子樹,且該樹根節點權值賦左右子樹權值之和;

④從森林F中刪除已選中的兩棵子樹,把新得到的二叉樹加入F中;

⑤重復③④,直到森林F中僅留下一棵樹時,得到最終的哈夫曼樹HF如圖所示:

⑥對上述哈夫曼樹,將其樹中的每個左分支賦0、右分支賦1,則從根結點開始到葉子結點,各分支路徑分別得到對應的二進制串,即為給定的每個字符的哈夫曼編碼。

二、代碼實現

編譯環境:Visual Studio 2019 16.6.3,代碼如下:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 6 //指定的編碼字符數
#define M (2 * N - 1) //HT結點數
#define MAXWEIGHT 100
typedef struct { //哈夫曼樹的存儲表示
	int weight;
	int parent, lchild, rchild;
}HTNode;
typedef HTNode HuffmanTree[M + 1];
typedef char* HuffmanCode[N + 1]; //哈夫曼編碼表
void Select(HuffmanTree HT, int n, int& s1, int& s2);
void CreateHuffmanTree(HuffmanTree& HT, int* w, int n);
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC);
void PrintHC(HuffmanCode HC, char ch[]);

int main() {
	HuffmanTree HT;
	HuffmanCode HC;
	int w[N + 1];
	char ch[N + 1];

	printf("Please input %d characters & its weight(e.g. A 25):\n", N);
	for (int i = 1; i <= N; i++) {
		scanf("%c %d", &ch[i], &w[i]);
		getchar();
	}

	CreateHuffmanTree(HT, w, N);
	CreateHuffmanCode(HT, HC);
	PrintHC(HC, ch);

	return 0;
}

void Select(HuffmanTree HT, int n, int& s1, int& s2) {
	int i, min = 0, temp = MAXWEIGHT;
	for (i = 1; i <= n; i++) {
		if (!HT[i].parent) {
			if (temp > HT[i].weight) {
				temp = HT[i].weight;
				min = i; //最小值作為新樹左子樹
			}
		}
	}
	s1 = min;
	for (i = 1, min = 0, temp = MAXWEIGHT; i <= n; i++) {
		if ((!HT[i].parent) && i != s1) {
			if (temp > HT[i].weight) {
				temp = HT[i].weight;
				min = i; //次小值作為新樹右子樹
			}
		}
	}
	s2 = min;
}

void CreateHuffmanTree(HuffmanTree& HT, int* w, int n) { //創建哈夫曼樹HT
	int i, s1, s2;
	if (n <= 1) return; //樹為空或僅有根節點
	for (i = 1; i <= M; ++i) { //初始化
		HT[i].weight = w[i];
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
	}
	for (i = n + 1; i <= M; ++i) { //創建新二叉樹
		Select(HT, i - 1, s1, s2);
		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;
	}
}

void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC) {
	int i, start, c, f;
	char cd[N];
	cd[N - 1] = '\0'; //編碼結束符
	for (i = 1; i <= N; i++) { //對於第i個待編碼字符即第i個帶權值的葉子節點
		start = N - 1; //開始start指向結束符位置
		c = i;
		f = HT[i].parent; //f指向當前結點的雙親
		while (f) { //從葉上溯,到根節點跳出
			if (HT[f].lchild == c)
				cd[--start] = '0'; //左孩子
			else
				cd[--start] = '1'; //右孩子
			c = f;
			f = HT[f].parent;
		}
		HC[i] = new char[N - start];
		strcpy(HC[i], &cd[start]); //復制結果到編碼表,用於輸出
	}
}

void PrintHC(HuffmanCode HC, char ch[]) { //打印哈夫曼編碼表
	for (int i = 1; i <= N; i++)
		printf("\n%c: %s\n", ch[i], HC[i]);
}

輸出各個字母的哈夫曼編碼:

三、算法分析
1、哈夫曼編碼是最優前綴編碼:對包括N個字符的數據文件,分別以它們的出現概率構造哈夫曼樹,利用該樹對應的哈夫曼編碼對報文進行編碼,得到壓縮后的最短二進制編碼;
2、算法自底而上地構造出對應最優編碼的二叉樹HT,它從n個葉子結點開始,識別出最低權重的兩個對象,並將其合並;當合並時,新對象的權重設置為原兩個對象權值之和,一共執行了|n| - 1次合並操作;
3、是貪心算法:每次選擇均為當下的最優選擇,通過合並來構造最優樹得到對應的哈夫曼編碼;
4、復雜度:
①時間復雜度:有n個終端節點,構成的哈夫曼樹總共有2n-1個節點,Select函數的時間復雜度為O(n),嵌套for循環,因此建立哈夫曼樹的時間復雜度為O(n^2), 創建哈弗曼編碼表的時間復雜度為O(n^2);
②空間復雜度:算法中臨時占用存儲空間大小與初始森林F中樹的個數n線性相關,n越大問題規模也越大,其耗費的空間復雜度為O(n)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM