一、哈夫曼樹的相關的幾個名詞
1、路徑:在一棵樹中,一個結點到另一個結點之間的通路,稱爲路徑。如下圖根結點到a結點之間的通路就是一條路徑。
2、路徑長度:在一條路徑中,每經過一個結點,路徑長度都要加1。如下圖根結點到b結點之間所經過的通路數爲2,也就是路徑長度爲2。
3、結點的權:每一個結點都有一個唯一的數值,稱爲結點的權。如下圖結點a的權值爲7,b的權值爲5。
4、結點的帶權路徑長度:根結點到結點間的路徑長度*結點的權值。如圖1結點b的帶權路徑長度=2*5=10。
5、樹的帶權路徑長度(WPL):樹中所有葉子結點的帶權路徑長度之和。如圖1的WPL=1*7+2*5+3*2+3*4=35。
二、什麼是哈夫曼樹?
在權爲w1,w2,...,wn的n各葉子結點的所有二叉樹中,樹的帶權路徑長度WPL最小的二叉樹稱爲最優二叉樹,也叫赫夫曼樹或哈夫曼樹。
//哈夫曼樹的結點結構
typedef struct {
char ch;///字符結點
int weight;///字符權值
int parent;///父母結點
int lchild;///左孩子在數組位置的下標
int rchild;///右孩子在數組位置的下標
}hnode;
三、如何構建哈夫曼樹?
在構建哈夫曼樹時,只需遵循一個原則:權值越大的結點離根結點越近。(權值最大就作爲根結點)
1、構建哈夫曼樹的過程:
(1)在n個結點權值中選出最小和次最小的結點權值,兩結點組成一個新的二叉樹,其根結點的權值爲左右孩子結點的權值和。
(2)在原有的n個結點權值中刪除最小和次最小·,將新權值加入到n-2個權值的行列中。
(3)重複(1)、(2),直到所有結點構成一棵二叉樹爲止,這棵樹就是哈夫曼樹。
2、構建哈夫曼樹:
哈夫曼樹表:
結點i | 字符 | 權值 | 左孩子 | 右孩子 | 雙親結點 |
0 | a | 7 | -1 | -1 | 6 |
1 | b | 5 | -1 | -1 | 5 |
2 | c | 2 | -1 | -1 | 4 |
3 | d | 4 | -1 | -1 | 4 |
4 | 6 | 2 | 3 | 5 | |
5 | 11 | 1 | 4 | 6 | |
6 | 18 | 0 | 5 | -1 |
僞代碼實現:
void Great_hfmtree(HfmTree ht[], int n){///創建哈夫曼樹,n個結點
int m1, m2, x1, x2;
int i, j;
for (i = 0; i<2 * n - 1; i++){///初始化
ht[i].weight = 0;
ht[i].parent = -1;
ht[i].lchild = -1;
ht[i].rchild = -1;
}
cout<<"<>輸入字符結點:";
for (int i = 0; i<n; i++)
cin >> ht[i].ch;
cout<<"<>輸入各字符結點對應的權值:";
for (i = 0; i<n; i++)
cin >> ht[i].weight;
for (i = 0; i<n - 1; i++){
x1 = x2 = MAXV;
m1 = m2 = 0;
for (j = 0; j<n + i; j++){///n+i爲原來結點+新增結點
if (ht[j].parent == -1 && hte[j].weight<x1){///找權值次最小結點
x2 = x1;///記錄x1,保證權值最小一定小於於權值次小
x1 = ht[j].weight;
m1 = j;///記錄權值次最小結點位置
}
else if (ht[j].parent == -1 && ht[j].weight<x2){///找權值最小結點
x2 = ht[j].weight;
m2 = j;///記錄權值最小結點位置
}
}
ht[m1].parent = n + i;
ht[m2].parent = n + i;
ht[n + i].weight = ht[m1].weight + ht[m2].weight;///權值最小結點和權值次最小結點之和
ht[n + i].lchild = m1;
ht[n + i].rchild = m2;
}
}
四、哈夫曼編碼
哈夫曼編碼:一棵哈夫曼樹中,規定哈夫曼樹的左分支代表0,右分支代表1,則從根結點到每個葉結點所經過的路徑分支組成的0和1序列便爲該結點對應字符的編碼。如下圖
代碼實現方法①:
void Reverse(char c[]) //將字符串倒置
{
int k = 0;
char temp;
while (c[k + 1] != '\0')//獲取當前字符編碼的長度
{
k++;
}
for (int i = 0; i <= k / 2; i++)//交換位置
{
temp = c[i];
c[i] = c[k - i];
c[k - i] = temp;
}
}
void HfmCode(HfmTree ht[], HCode hc[], int n)//從葉子結點到根結點遍歷編碼
{
hc = new HCode[n];
for (int i = 0; i<n; i++)
{
hc[i].data = ht[i].ch;
int ic = i;
int ip = ht[i].parent;
int k = 0;
while (ip != -1)
{
if (ic == ht[ip].lchild) //左孩子標'0'
hc[i].code[k] = '0';
else
hc[i].code[k] = '1'; //右孩子標'1'
k++;
ic = ip;
ip = ht[ic].parent;
}
hc[i].code[k] = '\0';
Reverse(hc[i].code);
}
for(int i=0;i<n;i++) cout<<"結點"<<hc[i].data<<"的字符編碼:"<<hc[i].code<<endl;
}
代碼實現方法②:
typedef char **huffmancode;///定義字符存儲編碼指針
void Haffmancode(HfmTree ht[], int n, huffmancode &hcode){
char *cd = new char[n];///申請內存空間,等同char*cd=(char*)malloc(n*sizeof(char));
hcode = new char *[n + 1];
cd[n - 1] = '\0';
int i, j, c, p;
for (i = 0; i<n; i++){
int start = n;
c = i;
p = ht[c].parent;
while (p != -1){
--start;
if (ht[p].lchild == c) cd[start] = '0';
else cd[start] = '1';
c = p;
p = ht[p].parent;
}
hcode[i] = new char[n - start];///分配內存空間或者建字符數組
strcpy(hcode[i], &cd[start]);///賦予字符串地址,因爲編碼數組範圍在[n-start,n-1].
}
for(int i=0;i<n;i++) cout<<"結點"<<hfmtree[i].ch<<"的字符編碼:"<<hcode[i]<<endl;
delete cd;///釋放內存空間
}
五、哈夫曼樹完整代碼的實現
#include<bits/stdc++.h>
using namespace std;
#define MAXV 10000
#define N 10000
typedef struct
{
char ch;
int weight;
int lchild, rchild, parent;
}HfmTree;
typedef struct
{
char data;
char code[100];
}HCode;
void Great_hfmtree(HfmTree ht[], int n){///創建哈夫曼樹,n個結點
int m1, m2, x1, x2;
int i, j;
for (i = 0; i<2 * n - 1; i++){///初始化
ht[i].weight = 0;
ht[i].parent = -1;
ht[i].lchild = -1;
ht[i].rchild = -1;
}
char x,y;
cout<<"<>輸入各字符結點:";
for (int i = 0; i<n; i++)
cin >> ht[i].ch;
cout<<"<>輸入各字符對應的權值:";
for (i = 0; i<n; i++)
cin >> ht[i].weight;
for (i = 0; i<n - 1; i++){
x1 = x2 = MAXV;
m1 = m2 = 0;
for (j = 0; j<n + i; j++){///n+i爲
if (ht[j].parent == -1 && ht[j].weight<x1){///找權值最小結點
x2 = x1;///記錄x1,保證權值最小一定大於權值次小
x1 = ht[j].weight;
m1 = j;///記錄權值最小結點位置
}
else if (ht[j].parent == -1 && ht[j].weight<x2){///找權值次最小結點
x2 = ht[j].weight;
m2 = j;///記錄權值次最小結點位置
}
}
ht[m1].parent = n + i;
ht[m2].parent = n + i;
ht[n + i].weight =ht[m1].weight + ht[m2].weight;///權值最小結點和權值次最小結點之和
ht[n + i].lchild = m1;
ht[n + i].rchild = m2;
}
}
void Reverse(char c[]) //將字符串倒置
{
int k = 0;
char temp;
while (c[k + 1] != '\0')
{
k++;
}
for (int i = 0; i <= k / 2; i++)
{
temp = c[i];
c[i] = c[k - i];
c[k - i] = temp;
}
}
void HfmCode(HfmTree ht[], HCode hc[], int n) //輸出哈弗曼編碼
{
hc = new HCode[n];
for (int i = 0; i<n; i++)
{
hc[i].data = ht[i].ch;
int ic = i;
int ip = ht[i].parent;
int k = 0;
while (ip != -1)
{
if (ic == ht[ip].lchild) //左孩子標'0'
hc[i].code[k] = '0';
else
hc[i].code[k] = '1'; //右孩子標'1'
k++;
ic = ip;
ip = ht[ic].parent;
}
hc[i].code[k] = '\0';
Reverse(hc[i].code);
}
for(int i=0;i<n;i++) cout<<"結點"<<hc[i].data<<"的字符編碼:"<<hc[i].code<<endl;
}
void TransCode(HfmTree ht[], HCode hc[], int n, char *s) //哈夫曼譯碼
{
cout << "解碼數據爲:";
int i = 2 * (n - 1); //根結點
while (*s != '\0')
{
if (*s == '0')
i = ht[i].lchild;
else
i = ht[i].rchild;
if (ht[i].lchild == -1)
{
cout << ht[i].ch;
i = 2 * n - 2;
}
s++;
}
cout << endl;
}
void menu(){
cout << "\t\t◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆" << endl;
cout << "\t\t* ********哈夫曼編碼/譯碼器******** *" << endl;
cout << "\t\t* 1.創建哈夫曼樹; *" << endl;
cout << "\t\t* 2.進行哈夫曼編碼; *" << endl;
cout << "\t\t* 3.進行哈夫曼譯碼; *" << endl;
cout << "\t\t* 4.退出; *" << endl;
cout << "\t\t◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆" << endl;
cout << "\t\t <注意>'+'代表' ','-'代表'!' " << endl;
cout << endl;
}
int main(){
HfmTree ht[N]={};
HCode hc[N];
char s[N];
int n;
char x,y;
menu();
system("color b0");
while (true){
///menu();
int op;
cout << "<>請選擇你要進行的功能<1.創建,2.編碼,3.譯碼,4.退出>:";
cin >> op;
switch (op){
case 1:{
cout<<"<>輸入結點個數:";
cin>>n;
Great_hfmtree(ht,n);
printf("創建哈夫曼樹成功!\n\n");
printf("結點i\t字符\t權值\t左孩子\t右孩子\t雙親結點\n");
for (int i = 0; i<2 * n - 1; i++)
cout << i << "\t" << ht[i].ch << "\t" << ht[i].weight << "\t" << ht[i].lchild << "\t" << ht[i].rchild << "\t" << ht[i].parent << endl;
puts("");
break;
}
case 2: {
HfmCode(ht,hc,n);
puts("");
break;
}
case 3: {
cout << "輸入要進行譯碼的字符串:";
cin >> s;
TransCode(ht, hc, n, s);
cout<<"譯碼成功!";
//cout << a << endl;
cout << endl;
break;
}
case 4:{
cout << "退出成功!" << endl;
exit(0);
break;
}
default:
break;
}
}
}
/*
顏色屬性由兩個十六進制數字指定 -- 第一個爲背景,第二個則爲
前景。每個數字可以爲以下任何值之一:
0 = 黑色 8 = 灰色
1 = 藍色 9 = 淡藍色
2 = 綠色 A = 淡綠色
3 = 淺綠色 B = 淡淺綠色
4 = 紅色 C = 淡紅色
5 = 紫色 D = 淡紫色
6 = 黃色 E = 淡黃色
7 = 白色 F = 亮白色
如果沒有給定任何參數,該命令會將顏色還原到 CMD.EXE 啓動時
的顏色。這個值來自當前控制檯窗口、/T 命令行開關或
DefaultColor 註冊表值。
如果用相同的前景和背景顏色來執行 COLOR 命令,COLOR 命令
會將 ERRORLEVEL 設置爲 1。
例如: "COLOR fc" 在亮白色上產生亮紅色
*/
運行結果如下: