解析C++哈夫曼樹編碼和解碼的實現_C 語言

來源:互聯網
上載者:User

一.背景介紹:

給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

二.實現步驟:

1.構造一棵哈夫曼樹

2.根據建立好的哈夫曼樹建立一張哈夫曼編碼錶

3.輸入一串哈夫曼序列,輸出原始字元

三.設計思想:

1.首先要構造一棵哈夫曼樹,哈夫曼樹的結點結構包括權值,雙親,左右孩子;假如由n個字元來構造一棵哈夫曼樹,則共有結點2n-1個;在構造前,先初始化,初始化操作是把雙親,左右孩子的下標值都賦為0;然後依次輸入每個結點的權值

2.第二步是通過n-1次迴圈,每次先找輸入的權值中最小的兩個結點,把這兩個結點的權值相加賦給一個新結點,,並且這個新結點的左孩子是權值最小的結點,右孩子是權值第二小的結點;鑒於上述找到的結點都是雙親為0的結點,為了下次能正確尋找到剩下結點中權值最小的兩個結點,每次迴圈要把找的權值最小的兩個結點的雙親賦值不為0(i).就這樣通過n-1迴圈下、操作,建立了一棵哈夫曼樹,其中,前n個結點是葉子(輸入的字元結點)後n-1個是度為2的結點

 3.編碼的思想是逆序編碼,從葉子結點出發,向上回溯,如果該結點是回溯到上一個結點的左孩子,則在記錄編碼的數組裡存“0”,否則存“1”,注意是倒著存;直到遇到根結點(結點雙親為0),每一次迴圈編碼到根結點,把編碼存在編碼錶中,然後開始編碼下一個字元(葉子)

4.解碼的思想是迴圈讀入一串哈夫曼序列,讀到“0”從根結點的左孩子繼續讀,讀到“1”從右孩子繼續,如果讀到一個結點的左孩子和右孩子是否都為0,如果是說明已經讀到了一個葉子(字元),翻譯一個字元成功,把該葉子結點代表的字元存在一個儲存翻譯字元的數組中,然後繼續從根結點開始讀,直到讀完這串哈夫曼序列,遇到結束符便退出翻譯迴圈

四.原始碼:

/***************************************目的:1.根據輸入的字元代碼集及其權值集,構造赫夫曼樹,輸出各字元的赫夫曼編碼2.輸入赫夫曼碼序列,輸出原始字元代碼作者:Dmego  時間:2016-11-11****************************************/#include<iostream>#define MAX_MA 1000#define MAX_ZF 100using namespace std;//哈夫曼樹的儲存表示typedef struct{ int weight; //結點的權值 int parent, lchild, rchild;//雙親,左孩子,右孩子的下標}HTNode,*HuffmanTree; //動態分配數組來儲存哈夫曼樹的結點 //哈夫曼編碼錶的儲存表示typedef char **HuffmanCode;//動態分配數組儲存哈夫曼編碼//返回兩個雙親域為0且權值最小的點的下標void Select(HuffmanTree HT, int n, int &s1, int &s2){ /*n代表HT數組的長度 */ //前兩個for迴圈找所有結點中權值最小的點(字元) for (int i = 1; i <= n; i++) {//利用for迴圈找出一個雙親為0的結點  if (HT[i].parent == 0)  {   s1 = i;//s1初始化為i   break;//找到一個後立即退出迴圈  } } for (int i = 1; i <= n; i++) {/*利用for迴圈找到所有結點(字元)權值最小的一個  並且保證該結點的雙親為0*/  if (HT[i].weight < HT[s1].weight && HT[i].parent == 0)   s1 = i; } //後兩個for迴圈所有結點中權值第二小的點(字元) for (int i = 1; i <= n; i++) {//利用for迴圈找出一個雙親為0的結點,並且不能是s1  if (HT[i].parent == 0 && i != s1)  {   s2 = i;//s2初始化為i   break;//找到一個後立即退出迴圈  }  } for (int i = 1; i <= n; i++) {/*利用for迴圈找到所有結點(字元)權值第二小的一個,  該結點滿足不能是s1且雙親是0*/  if (HT[i].weight < HT[s2].weight && HT[i].parent == 0 && i!= s1)   s2 = i; }}//構造哈夫曼樹void CreateHuffmanTree(HuffmanTree &HT, int n){/*-----------初始化工作-------------------------*/ if (n <= 1)  return; int m = 2 * n - 1; HT = new HTNode[m + 1]; for (int i = 1; i <= m; ++i) {//將1~m號單元中的雙親,左孩子,右孩子的下標都初始化為0  HT[i].parent = 0; HT[i].lchild = 0; HT[i].rchild = 0; } for (int i = 1; i <= n; ++i) {  cin >> HT[i].weight;//輸入前n個單元中葉子結點的權值 }/*-----------建立工作---------------------------*/ int s1,s2; for (int i = n + 1; i <= m; ++i) {//通過n-1次的選擇,刪除,合并來構造哈夫曼樹  Select(HT, i - 1, s1, s2);  /*cout << HT[s1].weight << " , " << HT[s2].weight << endl;*/  /*將s1,s2的雙親域由0改為i  (相當於把這兩個結點刪除了,這兩個結點不再參與Select()函數)*/  HT[s1].parent = i;  HT[s2].parent = i;  //s1,與s2分別作為i的左右孩子  HT[i].lchild = s1;  HT[i].rchild = s2;  //結點i的權值為s1,s2權值之和  HT[i].weight = HT[s1].weight + HT[s2].weight; }}//從葉子到根逆向求每個字元的哈夫曼編碼,儲存在編碼錶HC中void CreatHuffmanCode(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 開始指向最後,即編碼結束符位置  int c = i;  int f = HT[c].parent;//f指向結點c的雙親  while (f != 0)//從葉子結點開始回溯,直到根結點  {   --start;//回溯一次,start向前指向一個位置   if (HT[f].lchild == c) cd[start] = '0';//結點c是f的左孩子,則cd[start] = 0;   else cd[start] = '1';//否則c是f的右孩子,cd[start] = 1   c = f;   f = HT[f].parent;//繼續向上回溯  }  HC[i] = new char[n - start];//為第i個字元編碼分配空間  strcpy(HC[i], &cd[start]);//把求得編碼的首地址從cd[start]複製到HC的當前行中 } delete cd;}//哈夫曼解碼void TranCode(HuffmanTree HT,char a[],char zf[],char b[],int n){ /* HT是已經建立好的哈夫曼樹 a[]用來傳入二進位編碼 b[]用來記錄譯出的字元 zf[]是與哈夫曼樹的葉子對應的字元(葉子下標與字元下標對應) n是字元個數,相當於zf[]數組得長度 */ int q = 2*n-1;//q初始化為根結點的下標 int k = 0;//記錄儲存譯出字元數組的下標 int i = 0; for (i = 0; a[i] != '\0';i++) {//for迴圈結束條件是讀入的字元是結束符(二進位編碼)  //此代碼塊用來判斷讀入的二進位字元是0還是1  if (a[i] == '0')  {/*讀入0,把根結點(HT[q])的左孩子的下標值賦給q   下次迴圈的時候把HT[q]的左孩子作為新的根結點*/   q = HT[q].lchild;  }  else if (a[i] == '1')  {   q = HT[q].rchild;  }  //此代碼塊用來判斷HT[q]是否為葉子結點  if (HT[q].lchild == 0 && HT[q].rchild == 0)  {/*是葉子結點,說明已經譯出一個字元  該字元的下標就是找到的葉子結點的下標*/   b[k++] = zf[q];//把下標為q的字元賦給字元數組b[]   q = 2 * n - 1;//初始化q為根結點的下標   //繼續譯下一個字元的時候從哈夫曼樹的根結點開始  } } /*解碼完成之後,用來記錄譯出字元的數組由於沒有結束符輸出的 時候回報錯,故緊接著把一個結束符加到數組最後*/ b[k] = '\0';}//菜單函數void menu(){ cout << endl; cout << "  ┏〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓┓" << endl; cout << "  ┃  ★★★★★★★哈夫曼編碼與解碼★★★★★★★  ┃" << endl; cout << "  ┃     1. 建立哈夫曼樹      ┃" << endl; cout << "  ┃     2. 進行哈夫曼編碼      ┃" << endl; cout << "  ┃     3. 進行哈夫曼解碼      ┃" << endl; cout << "  ┃     4. 退出程式       ┃" << endl; cout << "  ┗〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓┛" << endl; cout << "      <><注意:空白字元用'- '代替><>" << endl; cout << endl;}void main(){ int falg;//記錄要編碼的字元個數 char a[MAX_MA];//儲存輸入的二進位字元 char b[MAX_ZF];//儲存譯出的字元 char zf[MAX_ZF];//儲存要編碼的字元 HuffmanTree HT = NULL;//初始化樹為空白數 HuffmanCode HC = NULL;//初始化編碼錶為空白表 menu(); while (true) {  int num;  cout << "<><請選擇功能(1-建立 2-編碼 3-解碼 4-退出)><>: ";   cin >> num;   switch (num)   {   case 1 :    cout << "<><請輸入字元個數><>:";    cin >> falg;    //動態申請falg個長度的字元數組,用來儲存要編碼的字元    /*char *zf = new char[falg];*/    cout << "<><請依次輸入" << falg << "個字元:><>: ";    for (int i = 1; i <= falg; i++)     cin >> zf[i];    cout << "<><請依次輸入" << falg << "個字元的權值><>: ";    CreateHuffmanTree(HT, falg);//調用建立哈夫曼樹的函數    cout << endl;    cout << "<><建立哈夫曼成功!,下面是該哈夫曼樹的參數輸出><>:" << endl;    cout << endl;    cout << "結點i"<<"\t"<<"字元" << "\t" << "權值" << "\t" << "雙親" << "\t" << "左孩子" << "\t" << "右孩子" << endl;    for (int i = 1; i <= falg * 2 - 1; i++)    {     cout << i << "\t"<<zf[i]<< "\t" << HT[i].weight << "\t" << HT[i].parent << "\t" << HT[i].lchild << "\t" << HT[i].rchild << endl;    }    cout << endl;    break;   case 2:    CreatHuffmanCode(HT, HC, falg);//調用建立哈夫曼編碼錶的函數    cout << endl;    cout << "<><產生哈夫曼編碼錶成功!,下面是該編碼錶的輸出><>:" << endl;    cout << endl;    cout << "結點i"<<"\t"<<"字元" << "\t" << "權值" << "\t" << "編碼" << endl;    for (int i = 1; i <= falg; i++)    {     cout << i << "\t"<<zf[i]<< "\t" << HT[i].weight << "\t" << HC[i] << endl;    }    cout << endl;    break;   case 3:    cout << "<><請輸入想要翻譯的一串二進位編碼><>:";    /*這樣可以動態直接輸入一串二進位編碼,    因為這樣輸入時最後系統會自動加一個結束符*/    cin >> a;    TranCode(HT, a, zf, b, falg);//調用解碼的函數,     /*這樣可以直接把數組b輸出,因為最後有     在數組b添加輸出時遇到結束符會結束輸出*/    cout << endl;    cout << "<><解碼成功!翻譯結果為><>:" << b << endl;    cout << endl;    break;   case 4:    cout << endl;    cout << "<><退出成功!><>" << endl;    exit(0);   default:    break;   } }  //-abcdefghijklmnopqrstuvwxyz //186 64 13 22 32 103 21 15 47 57 1 5 32 20 57 63 15 1 48 51 80 23 8 18 1 16 1 //000101010111101111001111110001100100101011110110}

五.運行截圖:

原文連結:http://www.cnblogs.com/dmego/p/6064069.html

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.