哈夫曼樹實踐

關於哈夫曼樹

1. 節點之間的路徑長度:在樹中從一個結點到另外一個結點所經歷的分支,構成了這兩個結點間的路徑上的通過的分支數稱爲它的路徑長度
2. 樹的路徑長度:從樹的根節點到樹中每一結點的路徑長度之和。在結點數目相同的二叉樹中,徹底二叉樹的路徑長度最短。
3. 結點的權:在一些應用中,賦予樹中結點的一個有某種意義的實數。
4. 結點的帶權路徑長度:結點到樹根之間的路徑長度與該結點上權的乘積
5. 樹的帶權路徑長度:定義爲樹中全部葉子結點的帶權路徑長度之和
6. 最優二叉樹:從已給出的目標帶權結點(單獨的結點) 通過一種方式的組合造成一棵樹.使樹的權值最小.。最優二叉樹是帶權路徑長度最短的二叉樹。根據結點的個數,權值的不一樣,最優二叉樹的形狀也各不相同。它們的共同點是:帶權值的結點都是葉子結點。權值越小的結點,其到根結點的路徑越長。html

哈夫曼樹的實現

哈夫曼樹的節點類

public class HuffmanTreeNode implements Comparable<HuffmanTreeNode> {
    private int weight;
    private HuffmanTreeNode parent, left, right;
    private char character;

    public HuffmanTreeNode(HuffmanTreeNode parent, HuffmanTreeNode left, HuffmanTreeNode right, int weight, char character) {
        this.weight = weight;
        this.character = character;
        this.parent = parent;
        this.left = left;
        this.right = right;
    }

    @Override
    public int compareTo(HuffmanTreeNode huffmanTreeNode) {
        return this.weight - huffmanTreeNode.getWeight();
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public HuffmanTreeNode getParent() {
        return parent;
    }

    public void setParent(HuffmanTreeNode parent) {
        this.parent = parent;
    }

    public HuffmanTreeNode getLeft() {
        return left;
    }

    public void setLeft(HuffmanTreeNode left) {
        this.left = left;
    }

    public HuffmanTreeNode getRight() {
        return right;
    }

    public void setRight(HuffmanTreeNode right) {
        this.right = right;
    }

    public char getCharacter() {
        return character;
    }

    public void setElement(char character) {
        this.character = character;
    }
}

哈夫曼樹的構造

構造哈夫曼樹是最重要的一步,由於它關係到編碼的0/1序號是否正確以及簡單
課堂上對於哈夫曼樹的理解不是很明白,經過這篇博客有了更深的了解哈夫曼樹(三)之 Java詳解node

有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設爲 w一、w二、…、wn,哈夫曼樹的構造規則爲:git

  1. 將w一、w二、…,wn當作是有n 棵樹的森林(每棵樹僅有一個結點);
  2. 在森林中選出根結點的權值最小的兩棵樹進行合併,做爲一棵新樹的左、右子樹,且新樹的根結點權值爲其左、右子樹根結點權值之和;
  3. 從森林中刪除選取的兩棵樹,並將新樹加入森林;
  4. 重複(02)、(03)步,直到森林中只剩一棵樹爲止,該樹即爲所求得的哈夫曼樹

構造哈夫曼樹須要對權值進行排序,每次取出兩個最小值並求和做爲父節點組成一顆小樹,如此重複直至只剩一棵樹即爲所求哈夫曼樹
排序有多種方法,這裏用的是堆排序:
按順序每次取兩個最小值,第一個做爲左孩子(編碼爲0),第二個做爲右孩子(編碼爲1)
而後求和,將其設爲父節點
繼續循環數組

具體過程如圖所示:
以{5,6,7,8,15}爲例,來構造一棵哈夫曼樹:
ide

public class HuffmanTree
{
    private HuffmanTreeNode mRoot;  // 根結點
    private String[] codes = new String[26];//用數組存放26個字母的機率(即權值)

    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(null,left,right,left.getWeight()+right.getWeight(),' ');//求左右孩子之和並存爲哈夫曼節點
            left.setParent(parent);
            right.setParent(parent);//將和設置爲左右孩子的父節點

            heap.addElement(parent);//將父節點添加到新的堆中(此時兩個孩子已經被刪除)
        }

        mRoot = parent;//循環結束,把最後一個父節點設置爲根結點
    }
}

哈夫曼編碼

根據前面所說的哈夫曼編碼規則
哈夫曼樹的左孩子對應0,右孩子對應1
因此只要能判斷根結點到葉子節點路徑上的全部節點是左仍是右孩子便可寫出對應編碼this

可是存在的問題就是,此那個根節點開始寫仍是從葉子節點寫
由於最終的編碼是從根結點開始寫起的,可是從根節點開始寫就沒法判斷哪條路徑是通向葉子節點,判斷會很麻煩
因此只要先將全部的葉子節點找出來存在數組裏,而後向上去找
經過編碼

if (node==node.getParent().getLeft())
                    stack.push(0);
                if (node==node.getParent().getRight())
                    stack.push(1);

來判斷當前節點是左孩子仍是右孩子.net

而後輸出棧裏的0/1,即爲正序的編碼3d

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.getCharacter()-'a';//x爲列表中元素轉化爲字母表中順序的索引值
            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;//result爲每個字母的哈夫曼編碼值,存放在對應的字母索引值x處
        }
        return codes;//codes爲存放每個字母編碼值的數組
    }

哈夫曼解碼

由於哈夫曼編碼是變長的,也就是每個元素對應的0/1個數不同長,沒法判斷哪幾位對應哪個元素
可是根據哈夫曼樹原理,仍然能夠經過一個一個的讀取0/1來進行判斷,由於一個0/1就對應了當前節點是左仍是右孩子
每讀取一次,就判斷當前節點有無孩子,而只要讀取到葉子節點,即找到了一個字母元素code

//從文件讀取
        Scanner scan1 = new Scanner(file1);
        String string = scan1.nextLine();//string爲原文編碼後的全部內容
        HuffmanTreeNode huffmanTreeNode = huffmanTree.getmRoot();//從根節點開始讀取
        //進行解碼
        String result2 = "";
        for (int i = 0; i < string.length(); i++) {
            if (string .charAt(i) == '0') {//讀取第一個數字判斷是否爲0(便是否爲左孩子)
                if (huffmanTreeNode.getLeft() != null) {//判斷當前節點是否有左孩子
                    huffmanTreeNode = huffmanTreeNode.getLeft();
                }
            } else {
                if (string .charAt(i) == '1') {
                    if (huffmanTreeNode.getRight() != null) {
                        huffmanTreeNode = huffmanTreeNode.getRight();
                    }
                }
            }
            if (huffmanTreeNode.getLeft() == null && huffmanTreeNode.getRight() == null) {//判斷當前節點是否爲葉子節點
                result2 += huffmanTreeNode.getCharacter();//是葉子節點即爲找到了一個字母,將其加入到解碼後的結果result2
                huffmanTreeNode = huffmanTree.getmRoot();//找到一個字母,即從根結點開始繼續循環找下一個字母
            }//若是一次循環沒找到,則繼續循環直到找到葉子節點,數字讀完即爲循環結束
        }

文章中字母出現的機率統計

侯澤洋同窗統計字母出現機率的方法很是好!
從中受益不淺
他直接將每一個字母減掉‘ a ’,對應ASCII表中的差值即爲當前字母對應26個字母中的位置索引
從而每個差值相等的字母都是相同的,且能知道這個字母是誰

Scanner scan = new Scanner(file);
        String s = scan.nextLine();
        int[] array = new int[26];
        for (int i = 0; i < array.length; i++) {
            array[i] = 0;//初始化
        }
        for (int i = 0; i < s.length(); i++) {
            char x = Character.toLowerCase(s.charAt(i));//將大寫字母轉換成小寫字母
            array[x - 'a']++;//相同字母的位置索引上的數字自增
        }
        System.out.println("打印各字母出現頻率:");
        for (int i = 0; i < array.length; i++) {
            System.out.println((char)('a'+i)+":"+(double) array[i] / s.length());
        }

完整代碼

【參考資料】
哈夫曼樹(最優二叉樹)及其Java實現
哈夫曼樹(三)之 Java詳解

相關文章
相關標籤/搜索