哈夫曼樹——二叉樹的應用(數據結構)

前言

哈夫曼樹是二叉樹的應用


一.幾個術語定義

在這裏插入圖片描述

1.路徑

由一結點到另一結點間的分支所構成。

(如1到4的路徑爲1到3,3到4這兩個分支構成)

2.路徑長度

路徑上的分支數目。

(如1到6的路徑長度爲3, 即1到3,3到5,5到6)

3.樹的外部路徑長度(EPL)

結點(外結點)到根結點的路徑長度之和。

(如這棵樹的葉子結點爲2、4、8、7,2的路徑長度爲1,4的路徑長度是2,8的路徑長度爲4,7的路徑長度是3,所以樹的外部路徑長度爲1+2+4+3=10)

4.樹的內部路徑長度( IPL )

葉結點(內結點)到根結點的路徑長度之和。

5.樹的路徑長度(PL)

PL= EPL+ IPL

IPL = 0+1+2+3 = 6
EPL = 1+2+3+4 = 10
PL = 16

在這裏插入圖片描述

6.權值

爲樹的葉結點賦予一個權值,一般用於表示出現頻度、概率值等。

(就是用於表示葉結點的那個圓圈裏的樹)

7.擴充二叉樹

只有度爲 2 的內結點和度爲 0的外結點。

(沒有度爲1的結點的二叉樹叫做擴充二叉樹)

8.結點的帶權路徑長度

結點到根的路徑長度與結點上權的乘積wi*li)。

(結點的帶權路徑長度就是該結點權值與該結點的路徑長度的乘積
如5那個結點的帶權路經長度爲 5*3=15)

9.樹的帶權路徑長度(WPL)

(我們一般關注的是樹的帶權路徑長度)

樹中所有葉子結點的帶權路徑長度之和。
在這裏插入圖片描述

二.哈夫曼樹的定義

對於具有不同帶權路徑長度的擴充二叉樹:
(具有相同權值的葉子結點,它組成了不同的二叉樹,那麼這四個結點形成的二叉樹它的帶權路徑長度是不同的)
在這裏插入圖片描述
• 對於同樣一組權值,放在外結點上,組織方式不同,帶權路徑長度也不同;
• 帶權路徑長度最小的擴充二叉樹叫做哈夫曼樹
• 哈夫曼樹中權值的結點離根最近
(權值最大的葉子結點越靠近根結點,權值最小的葉子結點越遠離根結點)

三.哈夫曼樹的構造

1.基本思想

使權的結點靠近

2.構造過程

例:

(1)以5個葉子結點爲例,我們把這個五個權值的結點作爲葉子,構成了一棵有五棵樹的森林。

在這裏插入圖片描述

(2)首先我們從這五棵樹的森林的集合中選取權值最小的兩棵樹,對它進行合併,2、4合併得到了6這個內結點,接下來把2,4刪掉,把6加入進這個集合,此時,這個森林中就包括4棵樹7、5、9、6。

在這裏插入圖片描述

(3)接下來,重複,從新的集合中找的最小的兩棵樹,一個是5,一個是6,在進行合併得到11,再把5、6從集合中刪除,把11加入,這個森林就包括7,9,11三棵樹。

在這裏插入圖片描述

(4)重複,仍然合併兩個最小的7、9得到16,把7、9刪除,插入16,這個森林包含兩棵樹11、16。

在這裏插入圖片描述

(5)同樣,把11和16合併得到27,把11和16刪除,再加入合併後的27,此時這個森林中只剩下一棵樹,這棵樹是一棵二叉樹,我們把這可樹叫做哈夫曼樹。

在這裏插入圖片描述
總結:
操作要點:對權值的合併、刪除與替換,總是合併當前值最小的兩個

3.構造算法

前提:
性質:一棵有n個葉子結點的Huffman樹有 2*n-1 個結點(2*n-1包括葉子結點和內結點)

存儲定義:
① 採用順序存儲結構(一維數組)
② 結點類型定義

typedef struct           //用順序存儲結構(一維數組)來描述結點的定義 
{ 
	int weght;           //權值 
	int parent,lch,rch;  //指向父結點,還有左孩子、右孩子標識的地址 
	
}*HuffmanTree;

例:
設n=4, w={70,50,20,40},m=2*4-1=7

①進行存儲四個葉子結點,並且把雙親左孩子右孩子的初始化都是零
在這裏插入圖片描述
在這裏插入圖片描述

②從中選出兩個最小的合併,刪除,插入重讀操作,改變各結點雙親、左孩子、右孩子的值。
在這裏插入圖片描述
在這裏插入圖片描述


在這裏插入圖片描述

在這裏插入圖片描述

代碼描述:

(把創建哈夫曼樹的過程封裝爲函數)


typedef struct           //用順序存儲結構(一維數組)來描述結點的定義 
{ 
	int weght;           //權值 
	int parent,lch,rch;  //指向父節點,還有左孩子、右孩子標識的地址 
	
}*HuffmanTree;

void CreatHuffmanTree (HuffmanTree HT,int n)
{ 
	if(n<=1) return;            //葉子小於等於1,不合適,不是哈夫曼樹 
	m=2*n-1;                    //先算出m,m表示總的結點個數 
	HT=new HTNode[m+1];         //然後動態分配了m+1個結點 
				//爲什麼創建m+1個呢?因爲0號單元未用,HT[m]表示根結點,從1到m表示這m個結點 
	for(i=1;i<=m;++i)           //對m個結點,對它的每一個左孩子右孩子雙親都初始化爲0 
	{	 
		HT[i].lch=0;
		HT[i].rch=0;
		HT[i].parent=0;
	}
	                     
	for(i=1;i<=n;++i)           //輸入所有葉子結點的權值 
		cin>>HT[i].weight;
		
	for( i=n+1;i<=m;++i)        //構造 Huffman樹:每次選兩個最小的,然後把它們的雙親、左孩子、右孩子改一下 
	{ 
		Select(HT,i-1, s1, s2);
				//每次(在這棵樹的葉子結點中)在HT[k](1≤k≤i-1)中選擇兩個其雙親域爲0,
				// 並且權值最小的結點,
				// 並返回它們在HT(數組)中的序號s1和s2
		HT[s1].parent=i; HT[s2] .parent=i; 
				//表示從F中刪除s1,s2  (改s1的雙親,改s2的雙親) 
		HT[i].lch=s1; HT[i].rch=s2 ; 
				//s1,s2分別作爲i的左右孩子    (改新創建的第i個的左孩子、右孩子) 
		HT[i].weight=HT[s1].weight + HT[s2] .weight;
				//i 的權值爲左右孩子權值之和  (它的權值是s1的權值加上s2的權值的和) 
	} 
}

四.哈夫曼編碼

1.主要用途

實現數據壓縮

例:給出一段報文(報文就是在網絡上傳輸的一些數據),報文中只包含字符集合 { C, A, S, T,H },各個字符出現的頻度(次數)是 W={ 2, 7, 4, 5,9 }(也就是權值)。

✓若給每個字符以等長編碼(3位二進制)
(因爲一共有5個字符,五個字符用二進制編碼肯定是要用三位才能編碼的,因爲2的3次方是8,5是小於8的但5也是大於4的)
✓ C : 000 A : 001 S : 010 T : 011 H:100
✓則發送這27(因爲5個字符一共出現了27次)個字符時,總編碼長度爲 ( 2+7+4+5+9 ) * 3 = 81。
✓能否減少總編碼長度,使得發出同樣報文,可以用最少的二進制代碼?
↓↓↓(我們考慮用哈夫曼樹)

2.設計哈夫曼編碼

例:字符集合 { C, A, S, T,H },出現的頻度(次數)是 W={ 2, 7, 4, 5,9 }。

(1)基本思想:
概率的字符用碼,概率的用碼,構造哈夫曼樹,寫出哈夫曼編碼。

(2)構造Huffman樹

①字符出現的概率{ 2/27, 7/27, 4/27, 5/27,9/27 },化整爲 { 2, 7, 4, 5,9 }作爲葉結點上的權值, 建立Huffman樹。
在這裏插入圖片描述

② 建好以後,左分支賦 0,右分支賦 1,得Huffman編碼(變長編碼)。

在這裏插入圖片描述

③寫出哈夫曼編碼

(概率就是權值)
在這裏插入圖片描述

✓總編碼長度(7+5+9)*2+( 2+4 )*3 = 60
✓比等長編碼的情形(81)要短。
(所以就實現了數據的壓縮)

(3)注意:
Huffman編碼是一種前綴編碼
即:任一個二進制編碼不是其它二進制編碼的前綴。解碼時不會混淆。

3.哈夫曼編碼小結

(1)哈夫曼編碼是不等長編碼

(2)哈夫曼編碼是前綴編碼,即任一字符的編碼都不是另一字符編碼的前綴。

(3)哈夫曼編碼樹中沒有度爲1的結點。若葉子結點的個數爲n,則哈夫曼編碼樹的結點總數爲 2n-1

(4)發送過程:根據由哈夫曼樹得到的編碼表送出字符數據。

(5)接收過程:按左0、右1的規定,從根結點走到一個葉結點,完成一個字符的譯碼。反覆此過程,直到接收數據結束。


後續

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章