哈夫曼樹的構造以及編碼實現
哈夫曼樹的介紹
Huffman Tree,中文名是哈夫曼樹或霍夫曼樹或者赫夫曼樹,它是最優二叉樹。
定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若樹的帶權路徑長度達到最小,則這棵樹被稱為哈夫曼樹。
(01) 路徑和路徑長度
定義:在一棵樹中,從一個結點往下可以達到的孩子或孫子結點之間的通路,稱為路徑。通路中分支的數目稱為路徑長度。若規定根結點的層數為1,則從根結點到第L層結點的路徑長度為L-1。
(02) 結點的權及帶權路徑長度
定義:若將樹中結點賦給一個有著某種含義的數值,則這個數值稱為該結點的權。結點的帶權路徑長度為:從根結點到該結點之間的路徑長度與該結點的權的乘積。
(03) 樹的帶權路徑長度
定義:樹的帶權路徑長度規定為所有葉子結點的帶權路徑長度之和,記為WPL。
哈夫曼樹的解析:
假設有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設為 w1、w2、…、wn,哈夫曼樹的構造規則為:
1. 將w1、w2、…,wn看成是有n 棵樹的森林(每棵樹僅有一個結點);
2. 在森林中選出根結點的權值最小的兩棵樹進行合併,作為一棵新樹的左、右子樹,且新樹的根結點權值為其左、右子樹根結點權值之和;
3. 從森林中刪除選取的兩棵樹,並將新樹加入森林;
4. 重複(02)、(03)步,直到森林中只剩一棵樹為止,該樹即為所求得的哈夫曼樹。
以{5,6,7,8,15}為例,來構造一棵哈夫曼樹。
第1步:建立森林,森林包括5棵樹,這5棵樹的權值分別是5,6,7,8,15。
第2步:在森林中,選擇根節點權值最小的兩棵樹(5和6)來進行合併,將它們作為一顆新樹的左右孩子(誰左誰右無關緊要,這裡,我們選擇較小的作為左孩子),並且新樹的權值是左右孩子的權值之和。即,新樹的權值是11。 然後,將"樹5"和"樹6"從森林中刪除,並將新的樹(樹11)新增到森林中。
第3步:在森林中,選擇根節點權值最小的兩棵樹(7和8)來進行合併。得到的新樹的權值是15。 然後,將"樹7"和"樹8"從森林中刪除,並將新的樹(樹15)新增到森林中。
第4步:在森林中,選擇根節點權值最小的兩棵樹(11和15)來進行合併。得到的新樹的權值是26。 然後,將"樹11"和"樹15"從森林中刪除,並將新的樹(樹26)新增到森林中。
第5步:在森林中,選擇根節點權值最小的兩棵樹(15和26)來進行合併。得到的新樹的權值是41。 然後,將"樹15"和"樹26"從森林中刪除,並將新的樹(樹41)新增到森林中。
此時,森林中只有一棵樹(樹41)。這棵樹就是我們需要的哈夫曼樹!
哈夫曼樹的基本操作:
在上述儲存結構上實現的哈夫曼演算法可大致描述為(設T的型別為HuffmanTree): (1)初始化 將T[0..m-1]中2n-1個結點裡的三個指標均置為空(即置為-1),權值置為0。 (2)輸入 讀入n個葉子的權值存於向量的前n個分量(即T[0..n-1])中。它們是初始森林中n個孤立的根結點上的權值。 (3)合併 對森林中的樹共進行n-1次合併,所產生的新結點依次放人向量T的第i個分量中(n≤i≤m-1)。每次合併分兩步: ①在當前森林T[0..i-1]的所有結點中,選取權最小和次小的兩個根結點[p1]和T[p2]作為合併物件,這裡0≤p1,p2≤i-1。 ② 將根為T[p1]和T[p2]的兩棵樹作為左右子樹合併為一棵新的樹,新樹的根是新結點T[i]。具體操作: 將T[p1]和T[p2]的parent置為i, 將T[i]的lchild和rchild分別置為p1和p2 新結點T[i]的權值置為T[p1]和T[p2]的權值之和。 注意: 合併後T[pl]和T[p2]在當前森林中已不再是根,因為它們的雙親指標均已指向了T[i],所以下一次合併時不會被選中為合併物件。
哈夫曼樹的儲存結構:
<span style="font-size:18px;">/*
哈夫曼樹的儲存結構,它也是一種二叉樹結構,
這種儲存結構既適合表示樹,也適合表示森林。
*/
typedef struct Node
{
int weight; //權值
int parent; //父節點的序號,為-1的是根節點
int lchild,rchild; //左右孩子節點的序號,為-1的是葉子節點
}HTNode,*HuffmanTree; //用來儲存哈夫曼樹中的所有節點
typedef char **HuffmanCode; //用來儲存每個葉子節點的哈夫曼編碼</span>
根據哈夫曼樹的構建步驟,寫出構建哈夫曼樹的程式碼如下:
<span style="font-size:18px;">/*
根據給定的n個權值構造一棵赫夫曼樹,wet中存放n個權值
*/
HuffmanTree create_HuffmanTree(int *wet,int n)
{
//一棵有n個葉子節點的赫夫曼樹共有2n-1個節點
int total = 2*n-1;
HuffmanTree HT = (HuffmanTree)malloc(total*sizeof(HTNode));
if(!HT)
{
printf("HuffmanTree malloc faild!");
exit(-1);
}
int i;
//以下初始化序號全部用-1表示,
//這樣在編碼函式中進行迴圈判斷parent或lchild或rchild的序號時,
//不會與HT陣列中的任何一個下標混淆
//HT[0],HT[1]...HT[n-1]中存放需要編碼的n個葉子節點
for(i=0;i<n;i++)
{
HT[i].parent = -1;
HT[i].lchild = -1;
HT[i].rchild = -1;
HT[i].weight = *wet;
wet++;
}
//HT[n],HT[n+1]...HT[2n-2]中存放的是中間構造出的每棵二叉樹的根節點
for(;i<total;i++)
{
HT[i].parent = -1;
HT[i].lchild = -1;
HT[i].rchild = -1;
HT[i].weight = 0;
}
int min1,min2; //用來儲存每一輪選出的兩個weight最小且parent為0的節點
//每一輪比較後選擇出min1和min2構成一課二叉樹,最後構成一棵赫夫曼樹
for(i=n;i<total;i++)
{
select_minium(HT,i,min1,min2);
HT[min1].parent = i;
HT[min2].parent = i;
//這裡左孩子和右孩子可以反過來,構成的也是一棵赫夫曼樹,只是所得的編碼不同
HT[i].lchild = min1;
HT[i].rchild = min2;
HT[i].weight =HT[min1].weight + HT[min2].weight;
}
return HT;
}</span>
上述程式碼中呼叫到了select_minium()函式,它表示從集合中選出兩個最小的二叉樹,下面列出兩種求兩個最小值的方法。
方法一程式碼如下:
<span style="font-size:18px;">/*
從HT陣列的前k個元素中選出weight最小且parent為-1的兩個,分別將其序號儲存在min1和min2中
*/
void select_minium(HuffmanTree HT,int k,int &min1,int &min2)
{
min1 = min(HT,k);
min2 = min(HT,k);
}</span>
這裡呼叫到的min()函式程式碼如下:
<span style="font-size:18px;">/*
從HT陣列的前k個元素中選出weight最小且parent為-1的元素,並將該元素的序號返回
*/
int min(HuffmanTree HT,int k)
{
int i = 0;
int min; //用來存放weight最小且parent為-1的元素的序號
int min_weight; //用來存放weight最小且parent為-1的元素的weight值
//先將第一個parent為-1的元素的weight值賦給min_weight,留作以後比較用。
//注意,這裡不能按照一般的做法,先直接將HT[0].weight賦給min_weight,
//因為如果HT[0].weight的值比較小,那麼在第一次構造二叉樹時就會被選走,
//而後續的每一輪選擇最小權值構造二叉樹的比較還是先用HT[0].weight的值來進行判斷,
//這樣又會再次將其選走,從而產生邏輯上的錯誤。
while(HT[i].parent != -1)
i++;
min_weight = HT[i].weight;
min = i;
//選出weight最小且parent為-1的元素,並將其序號賦給min
for(;i<k;i++)
{
if(HT[i].weight<min_weight && HT[i].parent==-1)
{
min_weight = HT[i].weight;
min = i;
}
}
//選出weight最小的元素後,將其parent置1,使得下一次比較時將其排除在外。
HT[min].parent = 1;
return min;
}</span>
方法二程式碼如下:
<span style="font-size:18px;">/*
從HT陣列的前k個元素中選出weight最小且parent為-1的兩個,分別將其序號儲存在min1和min2中
*/
void select_minium(HuffmanTree HT,int k,int &min1,int &min2)
{
int i = 0;
Int tempMin;
while(HT[i].parent != -1)
i++;
min1 = HT[i].weight;
while(HT[i].parent != -1)
i++;
min2 = HT[i].weight;
if(min1>min2)
{
tempMin = min1;
min1=min2;
Min2=tempMin;
}
//選出weight最小且parent為-1的元素,並將其序號賦給min
for(;i<k;i++)
{
if(HT[i].weight<min1 && HT[i].parent==-1)//min1始終儲存最小的,min2儲存第二小的資料
{
min2 = min1;
min1 = HT[i].weight;
}else if(HT[i].weight<min2 && HT[i].parent==-1)
{
Min2 = HT[i].weight;
}
}
}</span>
構建了赫夫曼樹,便可以進行赫夫曼編碼了,要求赫夫曼編碼,就需要遍歷出從根節點到葉子節點的路徑,下面給出兩種遍歷赫夫曼樹求編碼的方法。
1、 採用從葉子節點到根節點逆向遍歷求每個字元的赫夫曼編碼,程式碼如下:
<span style="font-size:18px;">/*
從葉子節點到根節點逆向求赫夫曼樹HT中n個葉子節點的赫夫曼編碼,並儲存在HC中
*/
void HuffmanCoding(HuffmanTree HT,HuffmanCode &HC,int n)
{
//用來儲存指向每個赫夫曼編碼串的指標
HC = (HuffmanCode)malloc(n*sizeof(char *));
if(!HC)
{
printf("HuffmanCode malloc faild!");
exit(-1);
}
//臨時空間,用來儲存每次求得的赫夫曼編碼串
//對於有n個葉子節點的赫夫曼樹,各葉子節點的編碼長度最長不超過n-1
//外加一個'\0'結束符,因此分配的陣列長度最長為n即可
char *code = (char *)malloc(n*sizeof(char));
if(!code)
{
printf("code malloc faild!");
exit(-1);
}
code[n-1] = '\0'; //編碼結束符,亦是字元陣列的結束標誌
//求每個字元的赫夫曼編碼
int i;
for(i=0;i<n;i++)
{
int current = i; //定義當前訪問的節點
int father = HT[i].parent; //當前節點的父節點
int start = n-1; //每次編碼的位置,初始為編碼結束符的位置
//從葉子節點遍歷赫夫曼樹直到根節點
while(father != -1)
{
if(HT[father].lchild == current) //如果是左孩子,則編碼為0
code[--start] = '0';
else //如果是右孩子,則編碼為1
code[--start] = '1';
current = father;
father = HT[father].parent;
}
//為第i個字元的編碼串分配儲存空間
HC[i] = (char *)malloc((n-start)*sizeof(char));
if(!HC[i])
{
printf("HC[i] malloc faild!");
exit(-1);
}
//將編碼串從code複製到HC
strcpy(HC[i],code+start);
}
free(code); //釋放儲存編碼串的臨時空間
}</span>
2、採用從根節點到葉子節點無棧非遞迴遍歷赫夫曼樹,求每個字元的赫夫曼編碼,程式碼如下:
<span style="font-size:18px;">/*
從根節點到葉子節點無棧非遞迴遍歷赫夫曼樹HT,求其中n個葉子節點的赫夫曼編碼,並儲存在HC中
*/
void HuffmanCoding2(HuffmanTree HT,HuffmanCode &HC,int n)
{
//用來儲存指向每個赫夫曼編碼串的指標
HC = (HuffmanCode)malloc(n*sizeof(char *));
if(!HC)
{
printf("HuffmanCode malloc faild!");
exit(-1);
}
//臨時空間,用來儲存每次求得的赫夫曼編碼串
//對於有n個葉子節點的赫夫曼樹,各葉子節點的編碼長度最長不超過n-1
//外加一個'\0'結束符,因此分配的陣列長度最長為n即可
char *code = (char *)malloc(n*sizeof(char));
if(!code)
{
printf("code malloc faild!");
exit(-1);
}
int cur = 2*n-2; //當前遍歷到的節點的序號,初始時為根節點序號
int code_len = 0; //定義編碼的長度
//構建好赫夫曼樹後,把weight用來當做遍歷樹時每個節點的狀態標誌
//weight=0表明當前節點的左右孩子都還沒有被遍歷
//weight=1表示當前節點的左孩子已經被遍歷過,右孩子尚未被遍歷
//weight=2表示當前節點的左右孩子均被遍歷過
int i;
for(i=0;i<cur+1;i++)
{
HT[i].weight = 0;
}
//從根節點開始遍歷,最後回到根節點結束
//當cur為根節點的parent時,退出迴圈
while(cur != -1)
{
//左右孩子均未被遍歷,先向左遍歷
if(HT[cur].weight == 0)
{
HT[cur].weight = 1; //表明其左孩子已經被遍歷過了
if(HT[cur].lchild != -1)
{ //如果當前節點不是葉子節點,則記下編碼,並繼續向左遍歷
code[code_len++] = '0';
cur = HT[cur].lchild;
}
else
{ //如果當前節點是葉子節點,則終止編碼,並將其儲存起來
code[code_len] = '\0';
HC[cur] = (char *)malloc((code_len+1)*sizeof(char));
if(!HC[cur])
{
printf("HC[cur] malloc faild!");
exit(-1);
}
strcpy(HC[cur],code); //複製編碼串
}
}
//左孩子已被遍歷,開始向右遍歷右孩子
else if(HT[cur].weight == 1)
{
HT[cur].weight = 2; //表明其左右孩子均被遍歷過了
if(HT[cur].rchild != -1)
{ //如果當前節點不是葉子節點,則記下編碼,並繼續向右遍歷
code[code_len++] = '1';
cur = HT[cur].rchild;
}
}
//左右孩子均已被遍歷,退回到父節點,同時編碼長度減1
else
{
HT[cur].weight = 0;
cur = HT[cur].parent;
--code_len;
}
}
free(code);
}</span>
相關推薦
Java理解實現哈夫曼樹以其編碼解碼
哈夫曼樹以其編碼解碼 要求: 1.從終端讀入字符集大小為n(即字元的個數),逐一輸入n個字元和相應的n個權值(即字元出現的頻度),建立哈夫曼樹,進行編碼並且輸出。 將它存於檔案hfmtree中(選做)。 2.利用已建好的哈夫曼編碼檔案hfmtree,對鍵盤輸入的正文進行譯碼。輸出字元正文
哈夫曼樹(優先佇列實現)
#include<iostream> #include<cstdio> #include<queue> using namespace std; int main(){ int t; cin>>t; while(t--)
哈夫曼樹的原理與實現
一、哈夫曼樹的介紹 Huffman Tree,中文名是哈夫曼樹或霍夫曼樹,它是最優二叉樹。 定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若樹的帶權路徑長度達到最小,則這棵樹被稱為哈夫曼樹。 這個定義裡面涉及到了幾個陌生的概念,下面就是一顆哈夫曼樹,我們來看圖解
哈夫曼樹構造
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Fri Jul 27 18:08:26 2018 @author: luogan """ # 樹節點類構建 class TreeNod
哈夫曼樹(Huffman Tree) 實現
假設有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設為 w1、w2、…、wn,則哈夫曼樹的構造規則為: (1) 將w1、w2、…,wn看成是有n 棵樹的森林(每棵樹僅有一個結點); (2) 在森林中選出兩個根結點的權值最小的樹合併,作為一棵新樹的左、右子樹,且
哈夫曼樹的構造以及編碼實現
哈夫曼樹的介紹 Huffman Tree,中文名是哈夫曼樹或霍夫曼樹或者赫夫曼樹,它是最優二叉樹。 定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若樹的帶權路徑長度達到最小,則這棵樹被稱為哈夫曼
轉載:哈夫曼樹的構造和哈夫曼編碼(C++代碼實現)
作者 pos blank 字符 element start man null == 作者:qiqifanqi 原文:http://blog.csdn.net/qiqifanqi/article/details/6038822 #include<stdio.h>
最小堆實現哈夫曼樹的構造及哈夫曼編碼、解碼
以下程式的演算法思想主要來自於浙江大學陳越老師主編的資料結構一書。最大堆(最小堆思想差不多)(之後會寫一篇部落格介紹),這裡主要講講哈夫曼樹的定義及實現。 Huffman Tree 相關概念: 結點的路徑長度:從根結點到該結點的路徑上分支的數
資料結構——哈夫曼樹的實現以及編碼(C語言實現)
1、問題描述 利用哈夫曼編碼進行通訊可以大大提高通道利用率,縮簡訊息傳輸時間,降低傳輸成本。構造哈夫曼樹時,首先將由n個字 符形成的n個葉子結點存放到陣列HuffNode的前n個分量中,然後根據哈夫曼方法的基本思想,不斷將兩個較小的子樹合併為一個
資料結構(15)--哈夫曼樹以及哈夫曼編碼的實現
參考書籍:資料結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社 1.哈夫曼樹 假設有n個權值{w1, w2, ..., wn},試構造一棵含有n個葉子結點的二叉樹,每個葉子節點帶權威wi,則其中帶權路徑長度WPL最小的二叉樹叫做最優二叉樹或者哈夫曼樹。 特點:
【哈夫曼樹】哈夫曼樹的實現以及哈弗曼編碼
基本概念 1、路徑和路徑長度 在一棵樹中,從一個結點往下可以達到的孩子或孫子結點之間的通路,稱為路徑。通路中分支的數目稱為路徑長度。若規定根結點的層數為1,則從根結點到第L層結點的路徑長度為L-1。 2、結點的權及帶權路徑長度 若將樹中結點賦給一個有著某
哈夫曼樹的構建、編碼以及帶權路徑長計算
給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。 構造哈夫曼樹的演算法如下:
資料結構之---C語言實現哈夫曼樹和編碼
//哈夫曼樹 //楊鑫 #include <stdio.h> #include <stdlib.h> typedef int ElemType; struct BTreeNode { ElemType data; struct BTr
哈夫曼樹以及檔案壓縮的實現
一、HuffmanTree 哈夫曼樹也稱為最優二叉樹,是加權路徑長度最短的二叉樹。在講述哈夫曼樹之前先給出幾個概念: 路徑:從一個結點到一個結點之間的分支構成這兩個結點之間的路徑 路徑長度:路徑上分支
哈夫曼樹的建立以及編碼
在codeblocks下編譯執行通過。 #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct{ int weight; int par
資料結構圖文解析之:哈夫曼樹與哈夫曼編碼詳解及C++模板實現
0. 資料結構圖文解析系列 1. 哈夫曼編碼簡介 哈夫曼編碼(Huffman Coding)是一種編碼方式,也稱為“赫夫曼編碼”,是David A. Huffman1952年發明的一種構建極小多餘編碼的方法。 在計算機資料處理中,霍夫曼編碼使用變長編碼表對源符號進行編碼,出現頻率較高的源符號採用較短的編碼,
哈夫曼樹,及哈夫曼編碼的構造
最近看到騰訊一個關於哈夫曼編碼的題目(如下) 某段文字中各個字母出現的頻率分別是{a:4,b:3,o:12,h:7,i:10},使用哈夫曼編碼,則哪種是可能的編碼:() a(000) b(001) h(01) i(10) o(11)a(0000) b(0001)
資料結構之二叉樹應用(哈夫曼樹及哈夫曼編碼實現)(C++)
一、哈夫曼樹1.書上用的是靜態連結串列實現,本文中的哈夫曼樹用 排序連結串列 實現;2.實現了從 字元頻率統計、構建權值集合、建立哈夫曼樹、生成哈夫曼編碼,最後對 給定字串的編碼、解碼功能。3.使用到的 “SortedList.h”標頭檔案,在上篇博文:資料結構之排序單鏈表。
哈夫曼樹與哈夫曼編碼(C語言程式碼實現)
在一般的資料結構的書中,樹的那章後面,著者一般都會介紹一下哈夫曼(HUFFMAN)樹和哈夫曼編碼。哈夫曼編碼是哈夫曼樹的一個應用。哈夫曼編碼應用廣泛,如 JPEG中就應用了哈夫曼編碼。 首先介紹什麼是哈夫曼樹。哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂
構造哈夫曼樹並求帶權路徑長度(c語言/CodeBlocks實現)
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <math.h>