資料結構::如何實現哈夫曼樹
【哈夫曼樹的定義】:
哈夫曼樹又稱為最優二叉樹,它是加權路徑最短的二叉樹。
不同於普通的二叉樹,它的每個節點都有相應的權值,當我們構建的時候最終離根節點遠的節點權重小,反之就比較大。
【哈夫曼樹的實現】:
1、利用的核心思想:貪心演算法
貪心演算法:是指在問題進行求解的時候,總是做出當前看起來最好的選擇。即就是說貪心演算法做出不是整體的最優解,而是某種意義上的區域性最優解。它不是對所有的問題都能得到整體最優解。
2、如何構建:
1)節點的設定:
我們瞭解哈夫曼樹的定義後,我們知道就比普通的二叉樹多了一個權重,因此,我們可以這樣進行設計。(注意:我在這裡給出了父節點可以不給出父節點,因為此時的構建兩個孩子節點就足夠了,我這裡加上也就是為了稍稍的複習下三叉鏈)
template<class T> struct HuffmanTreeNode { T _weight; HuffmanTreeNode<T>* _left; HuffmanTreeNode<T>* _right; HuffmanTreeNode<T>* _parent; HuffmanTreeNode(const T& x) :_left(NULL) ,_right(NULL) ,_parent(NULL) ,_weight(x) {} //此處的過載是後面的構建要用到,讀者先不用看,到後面知道如何構建的時候,再回過頭來串一遍 HuffmanTreeNode operator+(const HuffmanTreeNode& x) { return _weight+x._weight; } };
2)哈夫曼樹建構函式的實現
//建構函式 HuffmanTree() :_root(NULL) {} HuffmanTree(T*a, size_t n,const T& invalue) { //因為在構建哈夫曼數的時候,比較的是兩個權重的大小 //而權重的型別是Node*,所以要進行過載 struct com { bool operator()(Node* l,Node* r) { return l->_weight < r->_weight; } }; //利用堆來實現資料的儲存 Heap<Node* ,com> hp; for(size_t i = 0; i<n; i++) { if(a[i] != invalue) hp.Push(new Node(a[i])); } //然後再進行構建哈夫曼樹 //構建的最後,數組裡會只剩下一個元素 while(hp.Size()>1) { //先取出堆頂的元素給左孩子 Node* left = hp.Top(); //出堆 hp.Pop(); //再取出堆頂的元素給右孩子 Node* right = hp.Top(); //出堆 hp.Pop(); //然後左右構建出一個父節點,即兩者的權重之和 //(注意:此處需要進行過載+) Node* parent = new Node(left->_weight+right->_weight); //將節點要進行連線 parent->_left = left; parent->_right = right; //下面的這兩條語句用於構建三叉鏈 right->_parent = parent; left->_parent = parent; //要將構建完後的節點扔回來,因為它可能不是最小的 hp.Push(parent); } //出迴圈後,陣列中的儲存就剩最後的一個節點,此節點就是根 _root = hp.Top(); }
【說明】:
*這裡我寫的很詳細,我寫了兩個建構函式,一個是無參的,一個是通過陣列來進行構建。
*我們在構建哈夫曼樹的時候,是從陣列中選取兩個權重小的數,因此我們這裡就會出現各種各樣的辦法
方法一:首先想到的就是排序,如果我們利用排序的話,每次取出數之後,前面我說到每次取出兩個數進行處理後要將它們的父節點再扔回去,此時,當扔回去的時候,又要進行重新排序,所以利用陣列的話,代價會很大
方法二:利用遍歷,每次遍歷找出最小的一個,重複,它的時間複雜度是O(n^2),效率比較低,所以不建議採取
方法三:利用堆採用這種方法的時間複雜度是O(nlg(n)),效率會比較高,所以這裡我們採用這種方法
【說明】:此處我們要注意的一個點是,用堆的話,我們在堆裡要用什麼,怎麼放的問題?
我們如果在堆裡放節點的話就太大了(因為此處是結構體),應放節點的指標(來根據節點的權值 選擇。):即就是這裡的Heap<Node*>hp
*我這裡是用我自己實現的堆來進行實現的,讀者也可以用庫裡的STL 演算法中的幾個有關介面來進行實現
3)解構函式的實現
因為哈夫曼樹也是二叉樹,所以在析構的時候也要用到遞迴,和普通的二叉樹的析構一樣。
//解構函式
~HuffmanTree()
{
if(_root)
Destory(_root);
_root = NULL;
}
void Destory(Node* root)
{
if(root == NULL)
return;
Destory(root->_left);
Destory(root->_right);
delete root;
}
【哈夫曼樹的應用】:
1、哈夫曼編碼:
關於哈夫曼的應用,在哈夫曼樹構建好後,它的節點向左我們標記為0,向右我們標記為1,這樣每個節點到根節點都會有自己的一系列關於0,1的序列,次序列就是哈夫曼編碼。
2、經典應用:檔案壓縮
此處我就先不展開了,具體的我在後面開一篇部落格來詳細的進行講述,如何進行檔案壓縮