【哈夫曼樹】哈夫曼樹的實現以及哈弗曼編碼
基本概念
1、路徑和路徑長度
在一棵樹中,從一個結點往下可以達到的孩子或孫子結點之間的通路,稱為路徑。通路中分支的數目稱為路徑長度。若規定根結點的層數為1,則從根結點到第L層結點的路徑長度為L-1。
2、結點的權及帶權路徑長度
若將樹中結點賦給一個有著某種含義的數值,則這個數值稱為該結點的權。結點的帶權路徑長度為:從根結點到該結點之間的路徑長度與該結點的權的乘積。
3、樹的帶權路徑長度
樹的帶權路徑長度規定為所有葉子結點的帶權路徑長度之和,記為WPL。
4、哈夫曼樹
給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
構造哈夫曼樹
Huffman樹構造演算法:
1、由給定的n個權值{w1,w2,w3,…,wn}構造n棵只有根節點的擴充二叉樹森林F={T1,T2,T3,…,Tn},其中每棵擴充二叉樹Ti只有一個帶權值wi的根節點,左右孩子均為空。
2、重複以下步驟,直到F中只剩下一棵樹為止:
a、在F中選取兩棵根節點的權值最小的擴充二叉樹,作為左右子樹構造一棵新的二叉樹。將新二叉樹的根節點的權值為其左右子樹上根節點的權值之和。
b、在F中刪除這兩棵二叉樹;
c、把新的二叉樹加入到F中;
這樣最後得到哈夫曼樹。
結論:從上圖可以看出根節點的值為構建哈夫曼樹所有節點的值和16 = 7+5+3+1
取兩個值最小的值,可以用堆來實現。
#pragma once
#include <iostream>
#include <assert.h>
#include <queue>
#include <vector>
template <typename T>
struct HuffmanTreeNode
{
HuffmanTreeNode(const T &data)
: _weight(data)
, _pLeft(NULL)
, _pRight(NULL)
, _pParent(NULL)
{}
T _weight;
HuffmanTreeNode *_pLeft;
HuffmanTreeNode *_pRight;
HuffmanTreeNode *_pParent;
};
template <typename T>
struct greater
{
bool operator()(const T &left, const T &right)
{
return left->_weight > right->_weight;
}
};
template <typename T>
class HuffmanTree
{
public:
HuffmanTree(const T *weight, int size, const T &invalid)
: pRoot(NULL)
, _invalid(invalid)
{
assert(NULL != weight && size >= 0);
_Create(weight, size);
}
~HuffmanTree()
{
_Destroy(pRoot);
}
void LevelTraverse()
{
std::queue<HuffmanTreeNode<T> *> q;
if (NULL != pRoot)
q.push(pRoot);
while (!q.empty())
{
HuffmanTreeNode<T> *pCur = q.front();
q.pop();
std::cout << pCur->_weight << " ";
if (NULL != pCur->pLeft)
q.push(pCur->pLeft);
if (NULL != pCur->pRight)
q.push(pCur->pRight);
}
std::cout << std::endl;
}
HuffmanTreeNode<T> * GetRoot()
{
return pRoot;
}
private:
void _Destroy(HuffmanTreeNode<T> * &pRoot)
{
if (NULL != pRoot)
{
_Destroy(pRoot->_pLeft);
_Destroy(pRoot->_pRight);
delete pRoot;
pRoot = NULL;
}
}
void _Create(const T *weight, int size)
{
if (0 == size)
return;
else if (1 == size)
{
if (*weight != _invalid)
pRoot = new HuffmanTreeNode<T>(*weight);
}
else
{
std::priority_queue<HuffmanTreeNode<T> *, std::vector<HuffmanTreeNode<T>* >, greater<HuffmanTreeNode<T>*> >
heap;
for (int i = 0; i < size; ++i)
{
if (weight[i] != _invalid)
{
HuffmanTreeNode<T> *tmp = new HuffmanTreeNode<T>(weight[i]);
heap.push(tmp);
}
}
HuffmanTreeNode<T> *pLeft, *pRight;
while (heap.size() >= 2)
{
pLeft = heap.top();
heap.pop();
pRight = heap.top();
heap.pop();
HuffmanTreeNode<T> *pParent = new HuffmanTreeNode<T>(pLeft->_weight + pRight->_weight);
pParent->_pLeft = pLeft;
pParent->_pRight = pRight;
pLeft->_pParent = pParent;
pRight->_pParent = pParent;
heap.push(pParent);
}
if (!heap.empty())
pRoot = heap.top();
}
}
private:
HuffmanTreeNode<T> *pRoot;
T _invalid; //非法值
};
哈夫曼編碼
統計字元出現的個數,然後進行構建哈夫曼樹;
後序遍歷哈夫曼樹,左0右1,對每個葉子節點
注意:在建立不等長編碼時,必須是任何一個字元的編碼不能是另一個字元編碼的字首,這樣才能保證譯碼的唯一性。
任何一個字元的huffman編碼都不可能是另一個字元的huffman編碼的字首。