<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
什麼是哈夫曼樹?
把權值不同的n
個結點構造成一棵二元樹,如果此樹滿足以下幾個條件:
則稱符合上述條件的二元樹為最優二元樹,也稱為哈夫曼樹(Huffman Tree)。
構建哈夫曼樹的目的是什麼?
用來解決在通訊系統中如何使用最少的二進位制位編碼字元資訊。
本文將和大家聊聊哈夫曼樹的設計思想以及構建過程。
哈夫曼樹產生的背景:
在通訊系統中傳遞一串字串文字時,需要對這一串字串文字資訊進行二進位制編碼。編碼時如何保證所用到的bit
位是最少的,或保證整個編碼後的傳輸長度最短。
現假設字串由ABCD 4
個字元組成,最直接的想法是使用 2
個bit
位進行等長編碼,如下表格所示:
字元 | 編碼 |
---|---|
A | 00 |
B | 01 |
C | 10 |
D | 11 |
傳輸ABCD
字串一次時,所需bit
為 2
位,當通訊次數達到 n
次時,則需要的總傳輸長度為 n*2
。當字串的傳輸次數為 1000
次時,所需要傳輸的總長度為 2000
個bit
。
使用等長編碼時,如果傳輸的報文中有 26
個不同字元時,因需要對每一個字元進行編碼,至少需要 5
位bit
。
但在實際應用中,各個字元的出現頻率或使用次數是不相同的,如A、B、C
的使用頻率遠遠高於X、Y、Z
。使用等長編碼特點是無論字元出現的頻率差異有多大,每一個字元都得使用相同的bit
位。
哈夫曼的設計思想:
短碼
,使用頻率低的用長碼
,以優化整個資訊編碼的長度。不等長編碼
。哈夫曼不等長編碼的具體思路如下:
如現在要傳送僅由A、B、C、D 4
個字元組成的報文資訊 ,A
字元在資訊中佔比為 50%
,B
的佔比是 20%
,C
的佔比是 15%
, D
的 佔比是10%
。
不等長編碼的樸實思想是字元
的佔比越大,所用的bit
位就少,佔比越小,所用bit
位越多。如下為每一個字元使用的bit
位數:
A
使用 1
位bit
編碼。B
使用 2
位 bit
編碼。C
使用 3
位 bit
編碼。D
使用 3
位 bit
編碼。具體編碼如下表格所示:
字元 | 佔比 | 編碼 |
---|---|---|
A | 0.5 | 0 |
B | 0.2 | 10 |
C | 0.15 | 110 |
D | 0.1 | 111 |
如此編碼後,是否真的比前面的等長編碼所使用的總bit
位要少?
計算結果=0.5*1+0.2*2+0.15*3+0.1*3=1.65
。
先計算每一個字元在報文資訊中的佔比乘以字元所使用的bit
位。
然後對上述每一個字元計算後的結果進行相加。
顯然,編碼ABCD
只需要 1.65
個bit
,比等長編碼用到的2 個 bit
位要少 。當傳輸資訊量為 1000
時,總共所需要的bit
位=1.65*1000=1650 bit
。
哈夫曼編碼和哈夫曼樹有什麼關係?
因為字元的編碼是通過構建一棵自下向上的二元樹推匯出來的,如下圖所示:
哈夫曼樹的特點:
A
結點權值為0.5
,B
結點權值為0.2
,C
結點權值為0.15
,D
結點權值為 0.1
。0
和1
,然後順序連線從根結點到葉結點所有分支上的編號得到字元的編碼。相信大家對哈夫曼樹有了一個大概瞭解,至於如何通過構建哈夫曼樹,咱們繼續再聊。
在構建哈夫曼樹之前,先了解幾個相關概念:
1
,則從根結點到第L
層結點的路徑長度為L-1
。WPL
。如有權值為{3,4,9,15}
的 4
個結點,則可構造出不同的二元樹,其帶權路徑長度也會不同。如下 3
種二元樹中,B
的樹帶權路徑長度是最小的。
哈夫曼樹
的構建過程就是要保證樹的帶權路徑長度
最小。
那麼,如何構建二元樹,才能保證構建出來的二元樹的帶權路徑長度最小?
如有一字串資訊由 ABCDEFGH 8個字元組成,每一個字元的權值分別為{3,6,12,9,4,8,21,22}
,構建最優哈夫曼樹的流程:
1.以每一個結點為根結點構建一個單根二元樹,二元樹的左右子結點為空,根結點的權值為每個結點的權值。並儲存到一個樹集合中。
2.從樹集合中選擇根結點的權值最小的 2
個樹。重新構建一棵新二元樹,讓剛選擇出來的2
棵樹的根結點成為這棵新樹的左右子結點,新樹的根結點的權值為 2
個左右子結點權值的和。構建完成後從樹集合中刪除原來 2
個結點,並把新二元樹放入樹集合中。
如下圖所示。權值為 3
和4
的結點為新二元樹的左右子結點,新樹根結點的權值為7
。
3.重複第二步,直到樹集合中只有一個根結點為止。
當集合中只存在一個根結點時,停止構建,並且為最後生成樹的每一個非葉子結點的左結點分支標註0
,右結點分支標註1
。如下圖所示:
通過上述從下向上
的思想構建出來的二元樹,可以保證權值較小的結點離根結點較遠,權值較大的結點離根結點較近。最終二元樹的帶權路徑長度: WPL=(3+4)*5+6*4+(8+9+12)*3+(21+22)*2=232
。並且此樹的帶權路徑長度是所有可能構建出來的二元樹中最小的。
上述的構建思想即為哈夫曼樹設計思想,不同權值的字元編碼就是結點路徑上0
和1
的順序組合。如下表所述,權值越大,其編碼越小,權值越小,其編碼越大。其編碼長度即從根結點到此葉結點的路徑長度。
字元 | 權值 | 編碼 |
---|---|---|
A | 3 | 11110 |
B | 6 | 1110 |
C | 12 | 110 |
D | 9 | 001 |
E | 4 | 11111 |
F | 8 | 000 |
G | 21 | 01 |
H | 22 | 10 |
可以把權值不同的結點分別儲存在優先佇列(Priority Queue)中,並且給與權重較低的結點較高的優先順序(Priority)。
具體實現哈夫曼樹演演算法如下:
1.把n
個結點儲存到優先佇列中,則n
個節點都有一個優先權Pi
。這裡是權值越小,優先權越高。
2.如果佇列內的節點數>1
,則:
root
)。完整程式碼:
#include <iostream> #include <queue> #include <vector> using namespace std; //樹結點 struct TreeNode { //結點權值 float weight; //左結點 TreeNode *lelfChild; //右結點 TreeNode *rightChild; //初始化 TreeNode(float w) { weight=w; lelfChild=NULL; rightChild=NULL; } }; //為優先佇列提供比較函數 struct comp { bool operator() (TreeNode * a, TreeNode * b) { //由大到小排列 return a->weight > b->weight; } }; //哈夫曼樹類 class HfmTree { private: //優先佇列容器 priority_queue<TreeNode *,vector<TreeNode *>,comp> hfmQueue; public: //建構函式,構建單根結點樹 HfmTree(int weights[8]) { for(int i=0; i<8; i++) { //建立不同權值的單根樹 TreeNode *tn=new TreeNode(weights[i]); hfmQueue.push(tn); } } //顯示佇列中的最一個結點 TreeNode* showHfmRoot() { TreeNode *tn; while(!hfmQueue.empty()) { tn= hfmQueue.top(); hfmQueue.pop(); } return tn; } //構建哈夫曼樹 void create() { //重複直到佇列中只有一個結點 while(hfmQueue.size()!=1) { //從優先佇列中找到權值最小的 2 個單根樹 TreeNode *minFirst=hfmQueue.top(); hfmQueue.pop(); TreeNode *minSecond=hfmQueue.top(); hfmQueue.pop(); //建立新的二元樹 TreeNode *newRoot=new TreeNode(minFirst->weight+minSecond->weight); newRoot->lelfChild=minFirst; newRoot->rightChild=minSecond; //新二元樹放入佇列中 hfmQueue.push(newRoot); } } //按前序遍歷哈夫曼樹的所有結點 void showHfmTree(TreeNode *root) { if(root!=NULL) { cout<<root->weight<<endl; showHfmTree(root->lelfChild); showHfmTree(root->rightChild); } } //解構函式 ~HfmTree() { //省略 } }; //測試 int main(int argc, char** argv) { //不同權值的結點 int weights[8]= {3,6,12,9,4,8,21,22}; //呼叫建構函式 HfmTree hfmTree(weights); //建立哈夫曼樹 hfmTree.create(); //前序方式顯示哈夫曼樹 TreeNode *root= hfmTree.showHfmRoot(); hfmTree.showHfmTree(root); return 0; }
顯示結果:
上述輸出結果,和前文的演示結果是一樣的。
此演演算法的時間複雜度為O(nlogn)
。因為有n
個結點,所以樹總共有2n-1
個節點,使用優先佇列每個迴圈須O(log n)
。
除了上文的使用優先佇列之外,還可以使用一維陣列的儲存方式實現。
在哈夫曼樹中,葉子結點有 n
個,非葉子結點有 n-1
個,使用陣列儲存哈夫曼樹上所的結點需要 2n-1
個儲存空間 。其演演算法思路和前文使用佇列的思路差不多。直接上程式碼:
#include <iostream> using namespace std; //葉結點數量 const unsigned int n=8; //一維陣列長度 const unsigned int m= 2*n -1; //樹結點 struct TreeNode { //權值 float weight; //父結點 int parent; //左結點 int leftChild; //右結點 int rightChild; }; class HuffmanTree { public: //建立一維陣列 TreeNode hfmNodes[m+1]; public: //建構函式 HuffmanTree(int weights[8]); ~HuffmanTree( ) { } void findMinNode(int k, int &s1, int &s2); void showInfo() { for(int i=0; i<m; i++) { cout<<hfmNodes[i].weight<<endl; } } }; HuffmanTree::HuffmanTree(int weights[8]) { //前2 個權值最小的結點 int firstMin; int secondMin; //初始化陣列中的結點 for(int i = 1; i <= m; i++) { hfmNodes[i].weight = 0; hfmNodes[i].parent = -1; hfmNodes[i].leftChild = -1; hfmNodes[i].rightChild = -1; } //前 n 個是葉結點 for(int i = 1; i <= n; i++) hfmNodes[i].weight=weights[i-1]; for(int i = n + 1; i <=m; i++) { this->findMinNode(i-1, firstMin, secondMin); hfmNodes[firstMin].parent = i; hfmNodes[secondMin].parent = i; hfmNodes[i].leftChild = firstMin; hfmNodes[i].rightChild = secondMin; hfmNodes[i].weight = hfmNodes[firstMin].weight + hfmNodes[secondMin].weight; } } void HuffmanTree::findMinNode(int k, int & firstMin, int & secondMin) { hfmNodes[0].weight = 32767; firstMin=secondMin=0; for(int i=1; i<=k; i++) { if(hfmNodes[i].weight!=0 && hfmNodes[i].parent==-1) { if(hfmNodes[i].weight < hfmNodes[firstMin].weight) { //如果有比第一小還要小的,則原來的第一小變成第二小 secondMin = firstMin; //新的第一小 firstMin = i; } else if(hfmNodes[i].weight < hfmNodes[secondMin].weight) //如果僅比第二小的小 secondMin = i; } } } int main() { int weights[8]= {3,6,12,9,4,8,21,22}; HuffmanTree huffmanTree(weights); huffmanTree.showInfo(); return 1; }
測試結果:
哈夫曼樹是二元樹的應用之一,掌握哈夫曼樹的建立和編碼方法對解決實際問題有很大幫助。
以上就是漫談C++哈夫曼樹的原理及實現的詳細內容,更多關於C++哈夫曼樹的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45