一 哈夫曼樹
1.1 基本概念
- 算法思想
- 貪心算法(以局部最優,謀求全局最優)
- 適用范圍
- 1 【(約束)可行】:它必須滿足問題的約束
- 2 【局部最優】它是當前步驟中所有可行選擇中最佳的局部選擇
- 3 【不可取消】選擇一旦做出,在算法的后面步驟中,就無法再改變。
- 示例
- 【樹論:最優(二叉)數=帶權路徑最短的樹】
- 【圖論:最小(代價)生成樹】
- 【圖論:單源最短路徑=從某一結點出發至其他結點的帶權路徑之和最小 / 多源最短路徑=某一對結點之間的帶權路徑之和最小】
- 定義
- 哈夫曼樹 = 最優((滿)二叉)樹 = 帶權路徑之和最短的樹
- 哈夫曼樹所產生的哈夫曼編碼 = (最優)前綴編碼
1.2 算法描述/構造過程
1.3 算法實現
# define MAXNEGATIVE -9999 //最大負值
# define MAXPOSITIVE 9999 //最大正值
typedef struct HuffmanNode{ // 加 typedef : 避免發生編譯錯誤 " variable or field 'functionName' declared void "
double weight;
int parent, lchild, rchild; //當前節點的雙親結點、左右孩子結點
}*HuffmanTree;
- 2> 確定最小的兩個結點的選擇策略(貪婪策略)【次核心】
/**
* 選擇權值結點最小的兩個結點 s1、s2
*/
void Select(HuffmanTree &HT,int k,int *s1,int *s2){
int min[2]={0, 0}; // 升序排名, 保留 HT中 權重值最小的兩個結點的下標
cout<<"Select k:"<<k<<endl;
HT[0].weight = MAXPOSITIVE; // 初始化為最大正值,方便被 初始結點比最小而比下去
for(int i=1;i<=k;i++){
// cout<<"i:"<<i<<"\tweight:"<<HT[i].weight<<"\tparent:"<<HT[i].parent<<"\tparent.weight:"<<HT[HT[i].parent].weight<<endl;
if( HT[i].parent==0 && (HT[i].weight<HT[min[1]].weight)){
if(HT[i].weight<HT[min[0]].weight){ // 小於 最小者
min[1] = min[0]; min[0] = i; cout<<i<<"小於最小者!"<<endl;
} else {
min[1] = i;cout<<i<<"小於次小者!"<<endl; //僅小於 次小者
}
}
}
*s1 = min[0]; // 最小者下標
*s2 = min[1]; // 次小者下標
// cout<<"s1:"<<*s1<<"\ts2:"<<*s2<<endl;
}
/**
* 創建 Huffman 樹
* n : 初試結點(待編碼結點)數
*/
void CreateHuffmanTree(HuffmanTree &HT, int n){
int m = 2*n-1;
HT = new HuffmanNode[m+1]; // 0 位空余
cout<<"Please input initiative node's weight : "<<endl; // 輸入結點數據
for(int i=1;i<=n;i++){
cin>>HT[i].weight;
}
for(int k=1;k<=m;k++){ // 初始化所有結點
HT[k].lchild = 0;
HT[k].rchild = 0;
HT[k].parent = 0;
}
for(int j=n+1;j<=m;j++){
int s1,s2;
Select(HT, j-1, &s1, &s2); // 選擇權值結點最小的兩個結點s1、s2
HT[s1].parent = j; // 合並左右結點
HT[s2].parent = j;
HT[j].lchild = s1;
HT[j].rchild = s2;
HT[j].weight = HT[s1].weight + HT[s2].weight;
}
}
void OutputHuffmanTree(HuffmanTree &HT, int n){ // 輸出 哈夫曼樹 中 結點情況
int m = 2*n-1;
for(int i=1;i<=m;i++){
cout<<"<"<<i<<"> weight: "<<HT[i].weight<<"\t"<<"parent: "<<HT[i].parent<<"(weight:"<<HT[HT[i].parent].weight<<")\t";
cout<<"parent.lchild:"<<HT[HT[i].parent].lchild<<"(weight:"<<HT[HT[HT[i].parent].lchild].weight<<")"<<"\t";
cout<<"parent.rchild:"<<HT[HT[i].parent].rchild<<"(weight:"<<HT[HT[HT[i].parent].rchild].weight<<")"<<endl;
}
}
int main(){
HuffmanTree HT;
int n;
cout<<"Please input Huffman initiative Node Number:"; // 輸入哈夫曼結點初始結點數目 n
int s1, s2;
cin>>n;
CreateHuffmanTree(HT, n);
OutputHuffmanTree(HT, n);
return 0;
}
二 哈夫曼編碼
2.1 哈夫曼編碼的主要思想
2.2 【前綴編碼】
2.3 【哈夫曼編碼】
2.4 應用:文件的編碼與譯碼
2.5 哈夫曼編碼的算法實現 (根據哈夫曼樹,求哈夫曼編碼)
//由於 每個哈夫曼編碼是變長編碼;因此,使用指針數組來存儲每個字符編碼串的首地址
typedef char **HuffmanCode; // 動態分配數組,存儲哈夫曼編碼表 (char 前面不能隨意加 struct 關鍵字)
/**
* 根據哈夫曼樹,求哈夫曼編碼
*/
void CreateHuffmanCodeByHuffmanTree(HuffmanTree HT, HuffmanCode &HC,int n){//從葉子到根,逆向求每個字符的哈夫曼編碼
HC = new char *[n+1]; // 分配存儲n個字符編碼的編碼表空間
char *cd = new char [n]; // 分配臨時存放每個字符編碼的動態數組空間
cd[n-1] = '\0'; // 編碼結束符
for(int i=1;i<=n;i++){ // 逐個字符求哈夫曼編碼
int start = n-1; // start 初始化指向最后位置 即 編碼結束符位置,逆向存儲 從葉子到根結點路徑的0/1編碼
int c = i;
int f = HT[i].parent; // f 指向結點C 的雙親結點
while(f!=0){ // 直到 f 滑動到根節點為止
--start; // 編碼表游標向前滑動
if(HT[f].lchild == c){
cd[start] = '0';
} else {
cd[start]= '1';
}
c = f;
f = HT[f].parent; // 逆向, 自(樹)底向(樹)上
}
HC[i] = new char [n-start]; //為第i個字符編碼分配存儲空間
strcpy(HC[i], &cd[start]); // 頭文件: #include <string.h> 、<stdio.h>
}
delete cd;
}
void OutputHuffmanCodes(HuffmanCode &HC,int n){
for(int i=1;i<=n;i++){
cout<<"第"<<i<<"個哈夫曼編碼:";
for(int j=0,size=sizeof(HC[i])/sizeof(char);j<size;j++){
cout<<HC[i][j];
}
cout<<endl;
}
}
int main(){
HuffmanTree HT;
int n;
cout<<"Please input Huffman initiative Node Number:"; // 輸入哈夫曼結點初始結點數目 n
int s1, s2;
cin>>n;
CreateHuffmanTree(HT, n);
OutputHuffmanTree(HT, n);
HuffmanCode HC;
CreateHuffmanCodeByHuffmanTree(HT, HC, n);
OutputHuffmanCodes(HC, n);
return 0;
}
三 哈夫曼譯(解)碼
四 文獻
4.1 參考文獻
- 《數據結構(C語言版):嚴蔚敏/吳偉民》
- 《算法設計與分析基礎([美] Anany Levitin. 潘彥 譯)》
4.2 推薦文獻
五 二叉樹 補充:紅黑樹