C++數據結構_樹的理論學習筆記(3)_哈夫曼樹

前置:C++數據結構_樹的理論學習筆記(2)_存儲結構,二叉樹的實現

1.5 Huffman樹

1.5.1 Huffman樹的定義與存儲結構

1.Huffman樹的定義
        哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹樹的帶權路徑長度, 就是樹中所有葉結點的權值乘上其到根結點的路徑長度(若根結點爲0層,葉結點到根結點的路徑長度爲葉結點的層數)之和
        在這裏插入圖片描述
        圖(C)所示的二叉樹其帶權路徑長度最短,而且再也找不出比此二叉樹帶權路徑長度更短的二叉樹了,因此圖(C)所示是一棵哈夫曼樹

2.Huffman編碼
        (1)Huffman編碼根據字符出現的概率來構造平均長度最短的編碼,是一種變長的編碼。它的基本原理是頻繁使用的數據用較小的代碼代替,較少使用的數據用較大的編碼代替,每個數據的代碼不相同,但最終編碼的平均長度最小。
        (2)變長的編碼:Huffman編碼得到的字符編碼,其長度因符號出現的概率而有所不同
        (3)Huffman編碼的規則:從根結點到葉結點(包含原信息)的路徑,向左孩子前進編碼爲0,向右孩子前進編碼爲1.也可以反過來規定
        示例:
        在這裏插入圖片描述
        得到字符A、B、C、Z的編碼分別爲0、11、100、101.顯然Huffman編碼是前綴編碼,即任何一個字符的編碼都不是另一個字符編碼的前綴

3.Huffman樹的存儲結構
        以靜態散鏈表來存儲結點:

struct HNode
{
	int weight; //結點權值
	int parent; //雙親數組下標
	int LChild; //左孩子數組下標
	int RChild; //右孩子數組下標
};

        Huffman樹是一棵正則二叉樹(只有度爲0或2的結點的二叉樹)。根據二叉樹的性質,一棵n個葉子的Huffman樹共有2n-1個結點,可以用一個大小爲2n-1的一維數組存放Huffman樹的各個節點。
        如下圖(a)所示的Huffman樹有4個葉子結點,因此可以定義存儲結構爲:

HNode* HTree = new HNode[7];

        其存儲內容如下圖(b)所示,-1表示無孩子結點或雙親結點
在這裏插入圖片描述
        還需要設計Huffman編碼表對每個結點進行存儲。編碼表中各元素的C++描述如下:

struct HCode
{
	char data;
	string code;
};

        其中,data存儲結點的內存(這裏假設其數據類型爲char)。code數組存儲結點對應的編碼(這裏採用string存儲)。使用HCode類型定義一個一維數組,就可以存儲所有結點的編碼了。
        接下來就可以設計Huffman編碼的相關算法了,如構造、編碼、解碼算法。其C++類描述如下:

class Huffman
{
private:
	HNode* HTree; //Huffman樹
	HCode* HCodeTable; //存儲編碼表
	int N; //葉子結點數量
	void code(int i, string newcode); //遞歸函數,對第i個結點編碼
public:
	void CreateHTree(int a[], int n, char name[]); //創建Huffman樹
	void CreateCodeTable(); //創建編碼表
	void Encode(char* s, char* d); //編碼
	void Decode(char* s, char* d); //解碼
	~Huffman();
};

        其中,HTree存儲Huffman樹的結構,HCodeTable存儲每個結點的編碼內容。(假設要編碼的數據和編碼結果均爲字符串類型)

1.5.2 Huffman樹的構造

        構造Huffman樹的方法就是Huffman算法,描述如下:
        將n個帶權值的結點構成n棵二叉樹的集合T,每棵二叉樹只有一個根結點,其左右子樹都爲空:
        ①在T中選取兩個根結點權值最小的二叉樹作爲左右子樹,構成一棵新二叉樹。其根結點的權值爲左右子樹根結點權值之和;
        ②在T中刪除這兩棵樹,將新樹加入T;
        ③重複①②操作,直到T中僅存一棵樹,該樹即Huffman樹
        圖示如下:
在這裏插入圖片描述
        生成這樣的Huffman樹的C++描述:

//輸入參數a[]存儲每種字符的權值,n爲字符的種類,name爲各個字符的內容
void Huffman::CreateHTree(int a[], int n, char name[])
{
	N = n;
	HCodeTable = new HCode[N];
	HTree = new HNode[2 * n - 1]; //根據權重數組a[0..n-1]初始化Huffman樹
	for (int i = 0; i < N; i++)
	{
		HTree[i].weight = a[i];
		HTree[i].LChild = HTree[i].RChild = HTree[i].parent = -1;
		HCodeTable[i].data = name[i];
	}
	int x, y;
	for (int i = n; i < 2 * N - 1; i++) //開始建立Huffman樹
	{
		SelectMin(x, y, 0, i); //從1~i中選出兩個權值最小的結點的函數
		HTree[x].parent = HTree[y].parent = i;
		HTree[i].weight = HTree[x].weight + HTree[y].weight;
		HTree[i].LChild = x;
		HTree[i].RChild = y;
		HTree[i].parent = -1;
	}
}

1.5.3 Huffman編碼表的構建

        本文采用向上而下遞歸的處理方式,對每一個結點進行編碼。若子樹的根結點是其父結點的左分支則編碼“0”,若是右分支則編碼“1”,遞歸處理直到葉子結點。
        生成編碼表的C++描述如下:

void Huffman::code(int i, string newcode) //遞歸函數,對第i個結點編碼
{
	if (HTree[i].LChild == -1)
	{
		HCodeTable[i].code = newcode;
		return;
	}
	code(HTree[i].LChild, newcode + "0");
	code(HTree[i].RChild, newcode + "1");
}

void Huffman::CreateCodeTable() //生成編碼表
{
	code(2 * N - 2, "");
}

        如下圖(a)所示的Huffman樹,其對應編碼表可以是下圖(b)的結構
在這裏插入圖片描述

1.5.4 Huffman編、解碼的實現

        通常,對一段信息進行哈夫曼編碼時,需要對編碼的數據進行兩遍掃描.
(1)第一遍用來統計原數據中各字符出現的頻率,利用得到的頻率值創建哈夫曼樹,並要把樹的信息及編碼表保存起來,以便解壓時創建同樣的哈夫曼樹進行解壓;
(2)第二遍根據第一遍掃描得到的哈夫曼編碼表對原始數據進行編碼,並把編碼後得到的碼字存儲起來

1.編碼
        生成編碼表後,對於要編碼的字符串,每讀出一個字符,只要在編碼表中找出對應編碼即可;

2.解碼
        其基本思想是將編碼串從左到右逐位判別,直到確定一個字符。即從Huffman根節點開始,根據每一位是0或者1選擇左分支或右分支,直到到達葉子結點,至此每一個字符解碼結束。然後,再從根結點開始下一個字符的解碼
        解碼算法的C++描述如下:

void Huffman::Decode(char* s, char* d) //s爲編碼串,d爲解碼後的字符串
{
	while (*s != '\0')
	{
		int parent = 2 * N - 2; //根結點在HTree中的下標
		while (HTree[parent].LChild != -1) //如果不是葉子結點
		{
			if (*s == '0')
				parent = HTree[parent].LChild;
			else
				parent = HTree[parent].RChild;
			s++;
		}
		*d = HCodeTable[parent].data;
		d++;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章