數據結構之哈夫曼樹

數據結構之哈夫曼樹

實驗要求:

  • 設有字符集:S={a,b,c,d,e,f,g,h,i,j,k,l,m,n.o.p.q,r,s,t,u,v,w,x,y,z}。
    給定一個包含26個英文字母的文件,統計每一個字符出現的機率,根據計算的機率構造一顆哈夫曼樹。
    並完成對英文文件的編碼和解碼。
  • 要求:
    • 準備一個包含26個英文字母的英文文件(能夠不包含標點符號等),統計各個字符的機率
    • 構造哈夫曼樹
    • 對英文文件進行編碼,輸出一個編碼後的文件
    • 對編碼文件進行解碼,輸出一個解碼後的文件
    • 撰寫博客記錄實驗的設計和實現過程,並將源代碼傳到碼雲
    • 把實驗結果截圖上傳到雲班課html

      什麼是哈夫曼樹?

  • 基本概念
    • 最優二叉樹:平均編碼長度最短。
    • 結點之間的路徑:一個結點到另外一個結點所通過的結點次序。
    • 結點之間的路徑長度:兩個結點之間邊的條數。
    • 樹的路徑長度:從根結點到每一個葉子結點的路徑長度之和。
    • 帶權路徑: 路徑上加上的實際意義 。如汽車到下一站的距離咱們叫作權值.
    • 樹的帶權路經長度:每一個葉子結點到根的路徑長度權值之和,記做WPLnode

      仍是汽車的例子,汽車到達天津有2條路 能夠走。第一條路通過3個站,每一個站相距13km。第二條有2個站,每一個站相距18km。那麼有距離的路咱們叫作帶權路徑。根結點爲天津的樹,那麼第一條路帶權路徑爲 3*13 = 39,第二條爲2*18。樹的帶權路徑WPL 313+218.算法

  • 哈夫曼樹: 二叉樹是n個結點的結合,它的度(全部孩子個數的最大值)小於等於2。n個結點構成一個二叉樹有許多方法。使二叉樹的帶權路徑最小的樹,咱們叫作哈夫曼樹
  • 哈夫曼樹的特色:權值越大,所離根結點越近。

哈夫曼樹有什麼用?

  • 介紹了這麼多概念,不知道它有什麼用,讓初學者感受數據結構沒什麼勁。
    • 哈夫曼樹主要用在數據的壓縮如JPEG格式圖片,在通訊中咱們能夠先對發送的數據進行哈夫曼編碼壓縮數據提升傳輸速度。
    • 查詢優化:在工做中咱們咱們身邊放許多工具,因爲空間限制咱們不能把全部工具放在咱們最容易拿到的地方,全部咱們把使用頻率最高的工具放在最容易的位置。一樣的道理在查詢的時候咱們把查詢頻率最高的數據創建索引,這些都是使用了哈夫曼算法的思想。

怎麼構造哈夫曼樹?

  • 爲了理解怎麼構造哈夫曼樹咱們舉個例子:
    • 咱們如今有一組字符:{a,b,c,d,e,f,g,h,}
    • 他們出現的機率爲:{0.19, 0.21, 0.02, 0.03, 0.06, 0.07, 0.1, 0.32}.
    • 爲了讓咱們看起來清楚,咱們把它整數化::{19, 21, 2, 3, 6, 7, 10, 32}.
    • 即:8個結點的權值大小以下:
    • 從19,21,2,3,6,7,10,32中選擇兩個權小結點。選中2,3。同時算出這兩個結點的和5。
    • 從19,21,6,7,10,32,5中選出兩個權小結點。選中5,6。同時計算出它們的和11。
    • 從19,21,7,10,32,11中選出兩個權小結點。選中7,10。同時計算出它們的和17。
      注:這時選出的兩個數字都不是原來的二叉樹裏面的結點,因此要另外開一棵二叉樹。
    • 從19,21,32,11,17中選出兩個權小結點。選中11,17。同時計算出它們的和28。
    • 從19,21,32,28中選出兩個權小結點。選中19,21。同時計算出它們的和40。 另起一顆二叉樹。
    • 從32,28, 40中選出兩個權小結點。選中28,32。同時計算出它們的和60。
    • 從 40, 60中選出兩個權小結點。選中40,60。同時計算出它們的和100。 好了,此時哈夫曼樹已經構建好了。

構造哈夫曼樹會出現什麼問題?

  • 在課堂時間的時候我就出現了以問題:當一組數據中最小的兩個數加起來的時候若是出現和原來的一個數據相同怎麼辦?
  • 還有可能出現這種狀況:
  • 如今咱們來算一下這兩種結果的路徑長度:
    • WPL1=(3+5+7+8)*4+(11+14)*3+(23+29)*2=271
    • WPL2= (3+5)*+7*4+(8+11+14)*3+(23+29)*2=271
  • 通過計算,這兩種方式的路徑長度經計算獲得的WPL相等
  • 可是,爲了獲得統一的結果,咱們統一將生成相等放到與之相等的後面,也就是說咱們生成的結果爲第一種。

Huffman算法實現。

  • 構造哈夫曼樹結點:
    • 結點類咱們必須用幾個屬性來完善它,使它能更好地爲咱們工做:
    public class HuffmanTreeNode implements Comparable<HuffmanTreeNode>{
     private int weight;//結點所帶的權重,通常爲機率的大小
     private HuffmanTreeNode parent;//父結點
     private HuffmanTreeNode left;//左子結點
     private  HuffmanTreeNode right;//右子節點
     private char element;//元素值
     }
      //CompareTo 方法 用於比較結點之間的權重weight
      @Override
     public int compareTo(HuffmanTreeNode huffmanTreeNode) {
         if (this.weight>huffmanTreeNode.weight)
             return 1;
         else{
             if (this.weight<huffmanTreeNode.weight)
                 return -1;
             else
                 return 0;
         }
     }
  • 構造哈夫曼樹:
    • 首先構造一個根結點以及放26個英文字母的數組:
    private HuffmanTreeNode mRoot;   // 根結點
     private String[] codes = new String[26];
    • 而後構造哈夫曼樹:
    /**
      * 構造哈夫曼樹的方法
      * @param array 一個數組,裏面的數據是元素對應的權值
      */
     public HuffmanTree(HuffmanTreeNode[] array) {
         HuffmanTreeNode parent = null;
         ArrayHeap<HuffmanTreeNode> heap = new ArrayHeap();
         for (int i=0;i<array.length;i++)
         {
             heap.addElement(array[i]);
         }
    
         for(int i=0; i<array.length-1; i++) {
             //去除最小的兩個元素生成一棵樹
             HuffmanTreeNode left = heap.removeMin();
             HuffmanTreeNode right = heap.removeMin();
    
    
             parent = new HuffmanTreeNode(left.getWeight()+right.getWeight(),' ',null,left,right);
             left.setParent(parent);
             right.setParent(parent);
    
             heap.addElement(parent);
         }
    
         mRoot = parent;
     }
  • 編寫哈夫曼碼:
    • 首先編寫一箇中序遍歷的方法,把結點都放在數組中。
    protected void inOrder( HuffmanTreeNode node,
                             ArrayList<HuffmanTreeNode> tempList)
     {
         if (node != null)
         {
             inOrder(node.getLeft(), tempList);
             if (node.getElement()!=' ')
                 tempList.add(node);
    
             inOrder(node.getRight(), tempList);
         }
     }
    • 而後進行編碼:從下往上,若爲左孩子,則爲0,反之爲1,放入棧中,直至根節點,再將棧中元素所有取出,獲得該結點的編碼。
    public String[] getEncoding() {
         ArrayList<HuffmanTreeNode> arrayList = new ArrayList();
         inOrder(mRoot,arrayList);
         for (int i=0;i<arrayList.size();i++)
         {
             HuffmanTreeNode node = arrayList.get(i);
             String result ="";
             int x = node.getElement()-'a';
             Stack stack = new Stack();
             while (node!=mRoot)
             {
                 if (node==node.getParent().getLeft())
                     stack.push(0);
                 if (node==node.getParent().getRight())
                     stack.push(1);
    
                 node=node.getParent();
             }
             while (!stack.isEmpty())
             {
                 result +=stack.pop();
             }
             codes[x] = result;
         }
    
         return codes;
     }
  • 哈夫曼解碼:
    • 思想:從咱們產生的編碼文件中讀取編碼,若是遇到爲0,則指向左孩子,遇到1指向右孩子,直到指向葉子節點,即咱們要找的結點,而後又從根結點從新開始進行下面的編碼。
    • 代碼方法:
    for (int i = 0; i < s1.length(); i++) {
             if (s1.charAt(i) == '0') {
                 if (huffmanTreeNode.getLeft() != null) {
                     huffmanTreeNode = huffmanTreeNode.getLeft();
                 }
             } else {
                 if (s1.charAt(i) == '1') {
                     if (huffmanTreeNode.getRight() != null) {
                         huffmanTreeNode = huffmanTreeNode.getRight();
                     }
                 }
             }
             if (huffmanTreeNode.getLeft() == null && huffmanTreeNode.getRight() == null) {
                 result2 += huffmanTreeNode.getElement();
                 huffmanTreeNode = huffmanTree.getmRoot();
             }
         }
  • 寫入文件:
    • 方法代碼:
    File file2 = new File("F:\\HuffmanCode2.txt");
         FileWriter fileWriter1 = new FileWriter(file1);
         fileWriter1.write(result2);

運行結果:

參考資料

相關文章
相關標籤/搜索