#include <iostream> #include <iomanip> #include <string> #include <cstdlib> using namespace std; //定義哈夫曼樹存儲結構 typedef struct { char data; //存放結點數據 int weight; //記錄結點權值 int parent, lchild, rchild; }HTNode, * HuffmanTree; //哈夫曼編碼存儲表示 typedef char** HuffmanCode; //動態分配數組儲存哈夫曼編碼表 //初始化 HuffmanTree InitHuffman(HuffmanTree& HT, int n) { if (n < 0) { cout << "輸入初態結點數目不正確!!!" << endl; return NULL; } else { int m = 2 * n - 1; //n個葉子結點的哈夫曼樹有2n-1個結點 HT = new HTNode[m + 1]; //0號單元未用,申請m+1個空間 for (int i = 1; i <= m; i++) { HT[i].parent = 0; HT[i].lchild = 0; HT[i].rchild = 0; HT[i].weight = 0; HT[i].data = '-'; } //輸入初始數據,為了方便理解HT[0]不存放數據 cout << "請輸入初態結點數據及相應權值(格式形式:a 3):"; for (int i = 1; i <= n; i++) { cin >> HT[i].data; cin >> HT[i].weight; } return HT; } } //打印HT void PrintState(HuffmanTree& HT, int n) { int m = 2 * n - 1; //總結點數 cout << "結點i\tdata值\tweight\tparent\tlchild\trchild" << endl; for (int i = 1; i <= m; i++) { cout << setw(3) << i << "\t"; cout << setw(4) << HT[i].data << "\t"; cout << setw(4) << HT[i].weight << "\t"; cout << setw(4) << HT[i].parent << "\t"; cout << setw(4) << HT[i].lchild << "\t"; cout << setw(4) << HT[i].rchild << "\t" << endl; } } //選擇雙親域為0且兩權值最小的結點,選擇范圍為1到i-1 int* Select(HuffmanTree& HT, int n, int* Idx) { int MIN, MinSecond; //打擂台法使權最小值和次權最小值最大(假設第一個值權值最小無法進行) MIN = MinSecond = 99999; //循環從1到n次 for (int i = 1; i <= n; i++) { //如果雙親為0(未刪除結點與新生成結點),一定會執行if語句,尋找權最小值 if ((HT[i].parent == 0) && HT[i].weight < MIN) { //將最小的權值給MinSecond方便尋找次最小值 MinSecond = MIN; MIN = HT[i].weight; //記錄權最小值下標 Idx[0] = i; } //否則如果滿足條件尋找次最小值 else if ((HT[i].parent == 0) && HT[i].weight < MinSecond) { MinSecond = HT[i].weight; //記錄權次最小值下標 Idx[1] = i; } } return Idx; } //構造哈夫曼樹 void CreateHuffmanTree(HuffmanTree& HT, int n) { if (n <= 1) return; int m = 2 * n - 1; //n個葉子結點的哈夫曼樹有2n-1個結點 //從n+1開始構造哈弗曼樹 for (int i = n + 1; i <= m; i++) { //Idx用於存放兩個返回最小權值的下標,i-1為前n個結點,后面隨着i增加,n也增加 int* Idx = Idx = new int[2]; Idx = Select(HT, i - 1, Idx); int s1 = Idx[0]; //權最小值下標 int s2 = Idx[1]; //權次最小值下標 //給兩權值最小的結點置雙親,使s1,s2結點不在錄入Select范圍 HT[s1].parent = i; HT[s2].parent = i; //給新節點i左右孩子置位s1,s2 HT[i].lchild = s1; HT[i].rchild = s2; //給新結點賦權值 HT[i].weight = HT[s1].weight + HT[s2].weight; delete[]Idx; } } //哈夫曼編碼 void CreateHuffmanCode(HuffmanTree& HT, HuffmanCode& HC, int n) { HC = new char* [n + 1]; //分配字符的空間 char* TempCode = new char[n]; //分配臨時編碼表空間n個 TempCode[n - 1] = '\0'; //編碼有葉子結點開始逆序存放,先將末尾結束符標志打上,為后序strcpy //for循環逐個逆序求葉子結點的哈夫曼編碼 for (int i = 1; i <= n; i++) { int start = n - 1; //開始存放的起點指向末尾,用於后面HC拷貝是的起始地址 int NodeIdx = i; //NodeIdx最開始存放葉子結點序號 int ParentsIdx = HT[i].parent; //Parents指向其雙親結點序號 //若有雙親則由下到上進行編碼,編碼的字符,從序號1開始 while (ParentsIdx) { start--; //在一維數組末尾,strat先指向最后 //若雙親結點的左結點序號為NodeIdx,將0打入一維臨時數組中,否則打入1 if (HT[ParentsIdx].lchild == NodeIdx) TempCode[start] = '0'; else TempCode[start] = '1'; //結點的序號更新,進入上一層 NodeIdx = ParentsIdx; ParentsIdx = HT[NodeIdx].parent; } //臨時一維數組存放編碼成功,開始用HC順序存放字符編碼 HC[i] = new char[n - start]; //為序號為i的字符分配編碼空間 strcpy_s(HC[i], n - start, &TempCode[start]); //編碼字符串拷貝strcpy報錯因為沒有指定長度 } //打印哈夫曼編碼 cout << "---字符對應編碼----" << endl; for (int i = 1; i <= n; i++) { cout << HT[i].data << "\t\t" << HC[i] << endl; } delete []TempCode; } //輸入字符打印哈夫曼編碼 void PrintCode(HuffmanTree & HT, HuffmanCode & HC, int n) { char *str =new char[100]; //存儲需要解碼的字符 int flag = 0; //flag用於檢查輸入是否錯誤 cout << "請輸入需要編碼的字符:"; cin >> str; //匹配字符並打印相應的哈夫曼編碼 cout << "輸入的字符哈夫曼編碼為:"; for (int j = 0; j < strlen(str); j++) { flag = 0; for (int i = 1; i <= n; i++) { //匹配成功打印編碼 if (HT[i].data == str[j]) { cout << HC[i] ; flag = 1; //匹配成功 } } //如果有匹配失敗的情況,則跳出循環 if (flag == 0) { cout << "\n第" << j+1 << "字符輸入錯誤!" ; break; } } putchar(10); delete []str; //釋放str空間 } //哈夫曼解碼並打印 void HuffmanDecode(HuffmanTree& HT, int n) { char* str = new char[1024]; cout << "請輸入二進制編碼:"; cin >> str; int flag = 0; //用於檢查二進制編碼是否輸入錯誤 cout << "解碼對應字符為:"; //遍歷二進制編碼 for (int i = 0; i < strlen(str);) { int Root = 2 * n - 1; //Root為根結點序號 //當結點的左右孩子不為空時進入循環,由根結點進入 while (HT[Root].lchild && HT[Root].rchild) { if (str[i] == '0') Root = HT[Root].lchild; else if (str[i] == '1') Root = HT[Root].rchild; else { cout << "輸入的二級制編碼有誤!" << endl; flag = 1; break; } i++; //相后讀取二進制編碼,i值更新 } //如果找到錯誤跳出循環 if (flag) break; //打印編碼對應字符 cout << HT[Root].data; } delete []str; } int main() { int n; cout << "請輸入哈夫曼樹初態結點數:"; cin >> n; //初始化哈夫曼樹 HuffmanTree HT = InitHuffman(HT, n); //打印HT初態 cout << "--------------------HT初態--------------------" << endl; PrintState(HT, n); //構造哈夫曼樹 CreateHuffmanTree(HT, n); //打印HT終態 cout << "--------------------HT終態--------------------" << endl; PrintState(HT, n); HuffmanCode HC; //哈夫曼編碼-由下而上 CreateHuffmanCode(HT, HC, n); //打印字符對應編碼 PrintCode(HT, HC, n); //哈夫曼解碼並打印-由上而下 HuffmanDecode(HT, n); return 0; }
//由於編譯器版本原因strcpy出現不安全原因,導致無法運行,后使用strcpy_s給予拷貝長度得到解決;把“==”寫成“=”導致報錯;
/*
輸入字符串統計字符個數(權值)
int CreateWeightArray(char* str, int* Array) {
//初始化權值數組,128為str[i]的最大數值
for (int i = 0; i < 128; i++)
{
Array[i] = 0;
}
int length = 0;
//利用下標記錄位權
for (int i = 0; str[i]; i++)
{
Array[str[i]]++; //值加1,下標即字符
}
//統計字符串去重后的長度
for (int i = 0; i < 128; i++)
{
if (Array[i] != 0)
{
length++;
}
}
return length;
}
*/