1. 程式人生 > >【哈夫曼樹】哈夫曼樹的實現以及哈弗曼編碼

【哈夫曼樹】哈夫曼樹的實現以及哈弗曼編碼

基本概念

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編碼的字首。