哈夫曼樹與哈夫曼編碼
阿新 • • 發佈:2021-02-02
哈夫曼樹與哈夫曼編碼
哈夫曼樹
哈夫曼樹是一類帶權路徑長度最短的樹,通過對哈夫曼樹的構建,可以實現資料的編碼最短且不同資料編碼之間互不為前後綴的效果。
如何構建哈夫曼樹
- 根據給定的
n
n
n個權值
{
v
1
,
v
2
,
…
,
v
n
}
\{v_1,v_2,\dots,v_n \}
{v1,v2,…,vn}構造
n
n
n棵只有根節點的二叉樹,並令其權值為
w
i
(
i
=
1
,
2
,
…
,
n
)
w_i (i =1,2,\dots ,n)
wi
- 在這些二叉樹所組成的森林中選取兩棵節點權值最小的樹分別為其左右子樹(約定左子樹權值不大於右子樹)構建一棵新的二叉樹。
- 在森林中刪除選中的兩棵樹,並將合併後的二叉樹加入森林中。
- 重複 2 、 3 2、3 2、3步,直至只有一棵二叉樹,即哈夫曼樹。
舉個例子:
給定五個資料
{
A
,
B
,
C
,
D
,
E
}
\{A,B,C,D,E \}
{A,B,C,D,E},其權值分別為
{
1
,
3
,
5
,
2
,
4
}
\{1,3,5,2,4 \}
{1,3,5,2,4}:
構建哈夫曼樹的實現
程式碼中,將哈夫曼樹儲存在vector中進行演示; 若使用陣列形式,可在建構函式內動態申請陣列空間(記得在解構函式中釋放); 以下程式碼中使用了模板,其中K代表需要構建哈夫曼樹的資料的資料型別,V代表其權值的資料型別;
哈夫曼樹結構體的定義:
struct HTNode {
V value; //節點權值,用模板V代表其型別
K data;//節點所帶資料
int parent, lchild, rchild;//節點雙親及左右孩子下標
};
由於哈夫曼樹中沒有度為
1
1
1的節點,則一棵具有
n
n
n個葉子節點的哈夫曼樹共有
2
n
−
1
2n-1
2n−1個節點。
vector初始化:
擴容到
2
n
−
1
2n-1
2n−1個空間,並將所有單元中的雙親及左右孩子的下標初始化為
−
1
-1
−1,並將前
n
n
n個單元的權值及資料儲存下來。
建立哈夫曼樹:
進行迴圈
n
−
1
n-1
n−1次操作來實現選擇、刪除與合併操作來建立哈夫曼樹,其中:
- 選擇:從當前森林中選擇出雙親為 − 1 -1 −1且權值最小的兩個樹的根節點 s 1 s1 s1和 s 2 s2 s2。
- 刪除:將節點 s 1 s1 s1和 s 2 s2 s2的雙親修改為非 − 1 -1 −1。
- 合併:將 s 1 s1 s1和 s 2 s2 s2的權值和作為一個新的節點的權值,依次存放到容器的第 n n n個之後的單元中,同時記錄下這個節點的左孩子下標 s 1 s1 s1和右孩子下標 s 2 s2 s2。
計算樹的帶權路徑長度:
可以通過遞迴的方式向下進行
- 是葉子節點:返回該節點權值與深度乘積
- 非葉子節點:返回其左右子樹的帶權路徑長度之和
選擇出最小節點的函式:
void getMin(int n, int& s1, int& s2) {
s1 = s2 = -1;
int i;
//尋得兩個節點,並保證s1比s2節點小
for (i = 0; i < n; i++) {
if (ht[i].parent == -1) {
if (s1 == -1) {
s1 = i;
}
else {
s2 = i;
break;
}
}
}
if (ht[s1].value > ht[s2].value) {
s1 ^= s2 ^= s1 ^= s2;
}
//尋找到最小的兩個節點,並保證s2不小於s1
for (i += 1; i < n; i++) {
if (ht[i].parent == -1) {
if (ht[i].value < ht[s1].value) {
s2 = s1;
s1 = i;
}
else if (ht[i].value < ht[s2].value) {
s2 = i;
}
}
}
}
計算帶權路徑長度的函式:
int huffmanTreeWPL(int i, int dep) {
if (ht[i].lchild == -1 && ht[i].rchild == -1) {
//葉子節點的權值為該點權值*該節點深度
return ht[i].value * dep;
}
else {
//非葉子節點的權值為其子樹權值之和
return huffmanTreeWPL(ht[i].lchild, dep + 1) + huffmanTreeWPL(ht[i].rchild, dep + 1);
}
}
構建哈夫曼樹的函式:
HuffmanTree(vector<K>& HData, vector<V>& HValues) {
dataNum = HData.size();
maxNodes = dataNum * 2 - 1;
ht.clear();
//存入資料
HTNode tempNode;
tempNode.parent = tempNode.lchild = tempNode.rchild = -1;
for (int i = 0; i < maxNodes; i++) {
ht.push_back(tempNode);
}
for (int i = 0; i < dataNum; i++) {
ht[i].value = HValues[i];
ht[i].data = HData[i];
}
//構建哈夫曼樹
for (int i = dataNum; i < maxNodes; i++) {
int s1, s2;
getMin(i, s1, s2);
ht[s1].parent = ht[s2].parent = i;
ht[i].lchild = s1;
ht[i].rchild = s2;
ht[i].value = ht[s1].value + ht[s2].value;
}
//計算帶權路徑長度
WPL = huffmanTreeWPL(maxNodes - 1, 0);
}
哈夫曼編碼
從根節點開始,左子樹編碼為"0",右子樹編碼為"1",從根節點到該節點所經過的編碼連起來就是該節點的哈夫曼編碼。
舉個例子:(對剛才構建的哈夫曼樹進行編碼)
對資料進行哈夫曼編碼的函式:
void computedHuffmanCode() {
string code;
int parent;
//從葉子節點獲得逆序哈夫曼編碼
for (int i = 0; i < dataNum; i++) {
int j = i;
code = "";
while (ht[j].parent != -1) {
parent = ht[j].parent;
if (j == ht[parent].lchild) {
code = "0" + code;
}
else {
code = "1" + code;
}
j = parent;
}
//存入map中方便查詢
huffmanCode.insert(pair<K, string>(ht[i].data, code));
}
}
完整程式碼
#pragma once
#include <vector>
#include <map>
#include <string>
template <class K, class V>
class HuffmanTree {
public:
/**
*建構函式
*根據HValues的權值構建哈夫曼樹
*根據構建好的哈夫曼樹計算帶權路徑長度(WPL)和哈夫曼編碼
*/
HuffmanTree(std::vector<K>& HData, std::vector<V>& HValues) {
dataNum = HData.size();
maxNodes = dataNum * 2 - 1;
ht.clear();
//存入資料
HTNode tempNode;
tempNode.parent = tempNode.lchild = tempNode.rchild = -1;
for (int i = 0; i < maxNodes; i++) {
ht.push_back(tempNode);
}
for (int i = 0; i < dataNum; i++) {
ht[i].value = HValues[i];
ht[i].data = HData[i];
}
//構建哈夫曼樹
for (int i = dataNum; i < maxNodes; i++) {
int s1, s2;
getMin(i, s1, s2);
ht[s1].parent = ht[s2].parent = i;
ht[i].lchild = s1;
ht[i].rchild = s2;
ht[i].value = ht[s1].value + ht[s2].value;
}
//計算帶權路徑長度
WPL = huffmanTreeWPL(maxNodes - 1, 0);
//計算哈夫曼編碼
computedHuffmanCode();
}
/**
*返回帶權路徑長度
*/
int getWPL() {
return this->WPL;
}
/**
*返回全部哈夫曼編碼
*/
std::map<K, std::string> getAllHuffmanCode() {
return huffmanCode;
}
/**
*返回特定data的哈夫曼編碼
*該data不存在該哈夫曼樹中,返回空字串
*/
std::string getHuffmanCode(K data) {
return huffmanCode.find(data) == huffmanCode.end() ? "" : huffmanCode[data];
}
private:
struct HTNode {
V value;
K data;
int parent, lchild, rchild;
};
int dataNum; //需要編碼的資料數量
int maxNodes; //樹需要的最多節點數量
int WPL; //帶權路徑長度
std::vector<HTNode> ht; //哈夫曼樹
std::map<K, std::string> huffmanCode; //哈夫曼編碼
/**
*尋找當前兩個權值最小的樹
*/
void getMin(int n, int& s1, int& s2) {
s1 = s2 = -1;
int i;
//尋得兩個節點,並保證s1比s2節點小
for (i = 0; i < n; i++) {
if (ht[i].parent == -1) {
if (s1 == -1) {
s1 = i;
}
else {
s2 = i;
break;
}
}
}
if (ht[s1].value > ht[s2].value) {
s1 ^= s2 ^= s1 ^= s2;
}
//尋找到最小的兩個節點,並保證s2不小於s1
for (i += 1; i < n; i++) {
if (ht[i].parent == -1) {
if (ht[i].value < ht[s1].value) {
s2 = s1;
s1 = i;
}
else if (ht[i].value < ht[s2].value) {
s2 = i;
}
}
}
}
/**
*計算帶權路徑長度
*/
int huffmanTreeWPL(int i, int dep) {
if (ht[i].lchild == -1 && ht[i].rchild == -1) {
//葉子節點的權值為該點權值*該節點深度
return ht[i].value * dep;
}
else {
//非葉子節點的權值為其子樹權值之和
return huffmanTreeWPL(ht[i].lchild, dep + 1) + huffmanTreeWPL(ht[i].rchild, dep + 1);
}
}
/**
*計算哈夫曼編碼
*/
void computedHuffmanCode() {
std::string code;
int parent;
//從葉子節點獲得逆序哈夫曼編碼
for (int i = 0; i < dataNum; i++) {
int j = i;
code = "";
while (ht[j].parent != -1) {
parent = ht[j].parent;
if (j == ht[parent].lchild) {
code = "0" + code;
}
else {
code = "1" + code;
}
j = parent;
}
//存入std::map中方便查詢
huffmanCode.insert(std::pair<K, std::string>(ht[i].data, code));
}
}
};