【算法学习笔记】哈夫曼树的构建和哈夫曼编码的实现代码
介紹
哈夫曼(Haffman)這種方法的基本思想如下:
①由給定的n個權值{W1,W2,…,Wn}構造n棵只有一個葉子結點的二叉樹,從而得到一個二叉樹的集合F={T1,T2,…,Tn}。
②在F中選取根結點的權值最小和次小的兩棵二叉樹作為左、右子樹構造一棵新的二叉樹,這棵新的二叉樹根結點的權值為其左、右子樹根結點權值之和。
③在集合F中刪除作為左、右子樹的兩棵二叉樹,并將新建立的二叉樹加入到集合F中。
④重復②、③兩步,當F中只剩下一棵二叉樹時,這棵二叉樹便是所要建立的哈夫曼樹。
對于同一組給定葉子結點所構造的哈夫曼樹,樹的形狀可能不同,但帶權路徑長度值是相同的,一定是最小的。
哈夫曼樹的構建
為了方便操作,用靜態鏈表作為哈夫曼樹的存儲。 在構造哈夫曼樹時,設置一個結構數組HuffNode保存哈夫曼樹中各結點的信息,根據二叉樹的性質可知,具有n個葉子結點的哈夫曼樹共有2n-1個結點,所以數組HuffNode的大小設置為2n-1,結點的結構形式如下:
weight lchild rchild parent
其中,weight域保存結點的權值,lchild和rchild域分別保存該結點的左、右孩子結點在數組HuffNode中的序號,從而建立起結點之間的關系。為了判定一個結點是否已加入到要建立的哈夫曼樹中,可通過parent域的值來確定。初始時parent的值為-1,當結點加入到樹中時,該結點parent的值為其雙親結點在數組HuffNode中的序號,就不會是-1了。 構造哈夫曼樹時,首先將由n個字符形成的n個葉子結點存放到數組HuffNode的前n個分量中,然后根據前面介紹的哈夫曼方法的基本思想,不斷將兩個較小的子樹合并為一個較大的子樹,每次構成的新子樹的根結點順序放到HuffNode數組中的前n個分量的后面。
代碼:
#define maxvalue 1e6 //定義最大權值整數常量 #define maxleaf 1e3 //定義哈夫曼樹中結點個數整數常量 #define maxnode maxleaf*2-1 typedef struct{ int weight; int parent; int lchild; int rchild; }HNodeType; HNodeType* HuffTree(){ HNodeType node[maxnode]; int i,j,n; int m1,m2,x1,x2; cin>>n;//輸入葉子結點個數 for(int i = 0;i < n;i++) { node[i].weight=0; node[i].parent=-1; node[i].lchild=-1; node[i].rchild=-1;//初始化結點 } for(int i = 0;i < n;i++) { cin>>node[i].weight;//輸入n個葉子結點的權值 } for(int i = 0;i <n-1;i++){ m1=m2=maxvalue;//注意由于需要最小的和次小的兩個權值,因此需要設兩個變量 x1=x2=0;//一共n-1個葉節點,一共2n-1個結點 for(int j=0;j<n+i;j++){ //構造哈夫曼樹if(node[j].weight<m1&&node[j].parent=-1){ m2=m1; m1=node[j].weight; x2=x1;//保存結點的下標 x1=j;} else if(node[j].weight<m2&&node[j].parent=-1){ m2=node[j].weight; x2=j; } } //當結點加入到樹中時,該結點parent的值為其雙親結點在數組HuffNode中的序號,就不會是-1了 //現在合并兩棵子樹 步驟:更新兩棵子樹的父節點,修改父節點的權值,修改父節點的左右子樹信息 node[x1].parent=n+i; node[x2].parent=n+i;//最小權值的結點和倒數第二小的權值結點的雙親相同 node[n+i].weight=node[x1].weight+node[x2].weight;//記得更新父結點的權值 node[n+i].lchild=x1;//修改父節點的子樹 node[n+i].rchild=x2; } return node; }哈夫曼編碼
構造編碼的時候人們希望解決的兩個問題是:
①編碼總長最短。
②譯碼的唯一性。 哈夫曼樹可用于構造使電文的編碼總長最短的編碼方案。
具體做法如下:設需要編碼的字符集合為{d1,d2,…,dn},它們在電文中出現的次數或頻率集合為{w1,w2,…,wn},以d1,d2,…,dn作為葉子結點,w1,w2,…,wn作為它們的權值,構造一棵哈夫曼樹,規定哈夫曼樹中的左分支代表0,右分支代表1,則從根結點到每個葉子結點所經過的路徑分支組成的0和1的序列便為該結點對應字符的編碼,稱為哈夫曼編碼。
實現哈夫曼編碼的算法可分為兩大部分:
①構造哈夫曼樹。
②在哈夫曼樹上求葉結點的編碼。
求哈夫曼編碼,實質上就是在已建立的哈夫曼樹中,從葉子結點開始,沿結點的雙親鏈域退回到根結點,每退回一步,就走過了哈夫曼樹的一個分支,從而得到一位哈夫曼碼值。由于一個字符的哈夫曼編碼是從根結點到相應葉子結點所經過的路徑上各分支所組成的0、1序列,因此先得到的分支代碼為所求編碼的低位碼,后得到的分支代碼為所求編碼的高位碼。可以設置一結構數組HuffCode用來存儲各字符的哈夫曼編碼信息,數組元素的結構如下:
bit start
其中,分量bit為一維數組,用來保存字符的哈夫曼編碼,start表示該編碼在數組bit中的開始位置。所以,對于第i個字符,它的哈夫曼編碼存放在HuffCode[i].bit中的從HuffCode[i].start到n的分量上。
算法實現;(寫法一)
寫法二:
typedef HuffNode{ char data;//待編碼的符號 double weight;//符號出現的頻率 int parent,lchild,rchild; }HTnode,*HuffmanTree;編碼:從葉結點回退,左分支記0 右記1
void Code(HuffmanTree &HT,int n,int i,char *code) { //求第i個字符的編碼 int p,parent,start; char *cd; cd = new char[n]; cd[n-1] = '\0'; start = n-1; p = i;//p是當前結點的下標 parent = HT[i].parent;//當前結點的父節點下標 while(parent != -1) { if(HT[parent].lchild == p) cd[--start] = '0'; else cd[--start] = '1'; p = parent; parent = HT[patent].parent;//沿雙親回退 } strcpy(code,&cd[start]); delete[]cd; }總結
以上是生活随笔為你收集整理的【算法学习笔记】哈夫曼树的构建和哈夫曼编码的实现代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【算法学习笔记】二叉树的基本操作实现和应
- 下一篇: 【问题记录】进行mybatis实例查询测