哈夫曼樹的性質:
- 哈夫曼樹不唯一(即左孩子右孩子放的順序可以是左大右小也可以是左小右大)
- 哈夫曼樹的子樹也是哈夫曼樹
- 哈夫曼樹中無度為1的結點
- 有n個葉子結點的哈夫曼樹,其總結點數為2*n-1(非常重要!編程實現就要用這條性質)
總體思路:
- 對兩個最小者的選擇為雙親下標為0的結點
- 對選出的兩個最小者,修改雙親下標為新結點的下標
- 新結點的左右孩子修改為所選的兩個新結點的下標
- 新結點的權值為兩個結點的權值之和
結構體定義:
struct element { int weight; // 權值域 int lchild, rchild, parent; // 該結點的左、右、雙親結點在數組中的下標 };
哈夫曼樹類的定義:
class HTree { private: element *node; int n, m;//n是元素個數 stack<int> code; int setcode(int weight);//返回的是編碼的長度 public: HTree(int _n, int _weight[]); void select(int &s1, int &s2); void creatHT(); void print(); int search(int weight); void printHTcode(); };
構造函數的實現:
HTree::HTree(int _n,int _weight[]) { n = _n; m = 2 * n - 1; //n個元素需要2*n-1空間 node = new element[m]; //開辟m個大小的element空間 for (int i = 0; i <n; i++) { node[i].weight = _weight[i];//為每一個元素賦初始權值 } for (int i = 0; i < m; i++) { node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和雙親指針,都為0 } creatHT();//構造哈夫曼樹 }
選擇函數,比較結點中雙親為0的較小的兩個(在本文中s1最小):
void HTree::select(int &s1, int &s2) { //對兩個最小者的選擇為雙親下標為0的結點 for (int i = 0; i < n; i++) { if (node[i].parent == 0) { s1=i; //先找到一個臨時比較元素進行最小值比較 break; } } for (int i = 0; i <n; i++) //該循環是找到一個最小的並賦值給s1 { if ((node[s1].weight > node[i].weight) && (node[i].parent == 0)) //找雙親為0的 { s1 = i; } } //同s1的比較方法,先找到一個雙親為0的臨時值(排除s1),然后再進行逐一比較找到第二小的值賦值給s2即可 for (int i = 0; i <n; i++) { if ((node[i].parent == 0)&&i!=s1) //排除s1 { s2 = i; //先找到一個臨時比較元素進行最小值比較 break; } } for (int i = 0; i <n; i++) //找到除s1以外雙親為0中最小的並賦值給s2 { if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1)) { s2 = i; } } }
創建哈夫曼樹:
- 循環2*n-1 - n-1次,即進行m-n-1次比較即可
- 對兩個最小者的選擇為雙親下標為0的結點(調用選擇函數來實現)
- 對選出的兩個最小者,修改雙親下標為新結點的下標
- 新結點的左右孩子修改為所選的兩個新結點的下標
- 新結點的權值為兩個結點的權值之和
void HTree::creatHT() { for (int i = n; i < m; i++)//執行的是m-n次比較 { int s1, s2;//左小右大 select(s1, s2);//選擇數組中最小且雙親為0的結點 node[s1].parent = i;//找到后的s1,s2的雙親應該放在n-1后 node[s2].parent = i; node[i].lchild = s1; //左小 node[i].rchild = s2; //右大 node[i].weight = node[s1].weight + node[s2].weight; n++; } }
哈夫曼編碼:
原理:
- 輸入要編碼的權值,調用search函數遍歷查找到在數組中的地址並返回
- 從要編碼的位置上溯遍歷,並判斷是左孩子還是右孩子,左孩子為0右孩子為1(也可以反過來)
- 編碼是從上到下,而我們在遍歷的時候是從下到上,因此可以使用棧或者鏈表的頭插法進行存儲操作(我在這里使用的是棧,有點不好,輸出棧就被清空了,可能會影響時間效率)
- 判斷如果該結點的parent為空(即為0)停止循環即可
setcode(私有函數)
int HTree::setcode(int weight) { int location, parent, k;//location來定位權值所指向的位置,parent即Location的雙親,k是移動指針 int cont = 0; location = search(weight); k = location; parent = node[location].parent; while (node[k].parent!= 0&&k!=-1) { if (node[parent].lchild == k) { code.push(0); } else { code.push(1); } k = node[k].parent; parent = node[k].parent; cont++; } if (k == -1) { cout << "未找到!錯誤!" << endl; } return cont; }
search函數
int HTree::search(int weight) { int result = -1; for (int i = 0; i < m; i++) { if (node[i].weight == weight) { result = i; } } return result; }
完整代碼:

/* 1.對兩個最小者的選擇為雙親下標為0的結點 2.對選出的兩個最小者,修改雙親下標為新結點的下標 3.新結點的左右孩子修改為所選的兩個新結點的下標 4.新結點的權值為兩個結點的權值之和 */ #include<iostream> #include<iomanip> #include<stack> using namespace std; struct element { int weight; // 權值域 int lchild, rchild, parent; // 該結點的左、右、雙親結點在數組中的下標 }; class HTree { private: element *node; int n, m;//n是元素個數 stack<int> code; int setcode(int weight);//返回的是編碼的長度哼哼 public: HTree(int _n, int _weight[]); void select(int &s1, int &s2); void creatHT(); void print(); int search(int weight); void printHTcode(); }; int HTree::search(int weight) { int result = -1; for (int i = 0; i < m; i++) { if (node[i].weight == weight) { result = i; } } return result; } int HTree::setcode(int weight) { int location, parent, k;//location來定位權值所指向的位置,parent即Location的雙親,k是移動指針 int cont = 0; location = search(weight); k = location; parent = node[location].parent; while (node[k].parent!= 0&&k!=-1) { if (node[parent].lchild == k) { code.push(0); } else { code.push(1); } k = node[k].parent; parent = node[k].parent; cont++; } if (k == -1) { cout << "未找到!錯誤!" << endl; } return cont; } void HTree::printHTcode() { cout << "請輸入要查詢的編碼的權值並繼續:" << endl; int weight, size; cin >> weight; size=setcode(weight); cout << "權值 " << weight << "編碼長度: " << size << endl; cout << weight << " 編碼結果為: "; for (int i = 0; i < size; i++) { cout << code.top() << " "; code.pop(); } cout << endl; } HTree::HTree(int _n,int _weight[]) { n = _n; m = 2 * n - 1; //n個元素需要2*n-1空間 node = new element[m]; //開辟m個大小的element空間 for (int i = 0; i <n; i++) { node[i].weight = _weight[i];//為每一個元素賦初始權值 } for (int i = 0; i < m; i++) { node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和雙親指針,都為0 } creatHT();//構造哈夫曼樹 } void HTree::select(int &s1, int &s2) { //對兩個最小者的選擇為雙親下標為0的結點 for (int i = 0; i < n; i++) { if (node[i].parent == 0) { s1=i; //先找到一個臨時比較元素進行最小值比較 break; } } for (int i = 0; i <n; i++) //該循環是找到一個最小的並賦值給s1 { if ((node[s1].weight > node[i].weight) && (node[i].parent == 0)) //找雙親為0的 { s1 = i; } } //同s1的比較方法,先找到一個雙親為0的臨時值(排除s1),然后再進行逐一比較找到第二小的值賦值給s2即可 for (int i = 0; i <n; i++) { if ((node[i].parent == 0)&&i!=s1) //排除s1 { s2 = i; //先找到一個臨時比較元素進行最小值比較 break; } } for (int i = 0; i <n; i++) //找到除s1以外雙親為0中最小的並賦值給s2 { if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1)) { s2 = i; } } } void HTree::creatHT() { for (int i = n; i < m; i++)//執行的是m-n次比較 { int s1, s2;//左小右大 select(s1, s2);//選擇數組中最小且雙親為0的結點 node[s1].parent = i;//找到后的s1,s2的雙親應該放在n-1后 node[s2].parent = i; node[i].lchild = s1; //左小 node[i].rchild = s2; //右大 node[i].weight = node[s1].weight + node[s2].weight; n++; } } void HTree::print() { cout << "index weight parent lChild rChild" << endl; cout << left; // 左對齊輸出 for (int i = 0; i < m; ++i) { cout << setw(5) << i << " "; cout << setw(6) << node[i].weight << " "; cout << setw(6) << node[i].parent << " "; cout << setw(6) << node[i].lchild << " "; cout << setw(6) << node[i].rchild << endl; } cout << "哈夫曼樹打印完畢!" << endl; } int main() { cout << "請輸入要構造哈夫曼樹元素的個數: "; int n,*weight; cin >> n; weight = new int[n + 1]; cout << "請輸入這" << n << "個元素的權值:" << endl; for (int i = 0; i < n; i++) { cin >> weight[i]; } HTree test(n, weight); test.print(); test.printHTcode(); system("pause"); return 0; }
測試數據
4 5 2 4 7 4
結果:
請輸入要構造哈夫曼樹元素的個數: 4 請輸入這4個元素的權值: 5 2 4 7 index weight parent lChild rChild 0 5 5 0 0 1 2 4 0 0 2 4 4 0 0 3 7 6 0 0 4 6 5 1 2 5 11 6 0 4 6 18 0 3 5 哈夫曼樹打印完畢! 請輸入要查詢的編碼的權值並繼續: 4 權值 4編碼長度: 3 4 編碼結果為: 1 1 1 請按任意鍵繼續. . .
特別說明:
這個代碼絕對不是最優解:)
但是原理是對的,嘛畢竟自己能力有限~
只要原理懂了可以自己優化優化的~~
各位見笑了,還請多多包涵。