1. 程式人生 > >淺談哈夫曼樹的構建、遍歷、編碼

淺談哈夫曼樹的構建、遍歷、編碼

最近研究二叉樹,比較經典的樹就是哈夫曼樹了,所以研究一下它的構建以及哈夫曼編碼,惡補一下資料結構的知識。

有一段密文:aabbccabcacb,解析為電碼傳輸,只能為0、1來表示
例如
a 0
b 1
c 01
d 10
… …
那麼aabc….可以表示為00101,但是在解析的時候發現0 01 10可以出現混亂,001可以解析為 ac 或者 aab,這樣就會導致資料不唯一。因此可以用二叉樹來保證資料唯一。
這裡寫圖片描述

左邊用0表示,右邊用1表示,那麼abcd的編碼如下:
A 0
B 10
C 110
D 111
aabc….
可以表示為0010110,在解析的時候就不會出現混亂,因為資料是唯一表示的。

那麼問題又來了,如果a編碼出現的次數是1次,b編碼的出現次數是1次,c出現次數是10000
那麼aabc的資料長度:1*1 +1*1+2*1+3*10000+…
那麼如何進行優化,是這個編碼的長度最短?可以通過調整數節點的位置,例如下:

這裡寫圖片描述

左邊用0表示,右邊用1表示,那麼abcd的編碼如下:
B 0
A 10
C 110
D 111
那麼aabc…的編碼將會變成
2*1+2*1+1*10000+…
這樣就可以使得在保證編碼資料唯一的情況下,編碼長度最小。這就是一個哈夫曼編碼,這課樹就是哈夫曼樹

哈夫曼樹定義:指對於一組帶有確定權值的葉節點,構造的具有最小帶權路徑長度的二叉樹

Wpl=(w1 * l1+w2*l2 + ·····)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
構造哈夫曼樹:
步驟:先對陣列A排序,後選擇前兩個最小的值作為左右節點,它們之和是其根節點(父節點node),node入陣列A,直到陣列A只剩下一個元素。

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

結束迴圈
2 5 23 43 55 65 71 123

總度數:
Sum=2*5+5*5+23*4+43*3+55*3+65*3+71*2+123*2
=10+ 25+ 92 +129 +165 +195 +142 + 246
=1004度

程式碼實現:
/**
 * 類節點
 * @author breeze
 *
 * @param <E>
 */
class TreeNode<E> implements Comparable<TreeNode<E>>{ E data; int weight; TreeNode<E>left; TreeNode<E>right; TreeNode<E> parent; public TreeNode(E data,int weight) { this.data = data; this.weight=weight; left=null; right=null; parent=null; } } /** * 建立哈夫曼樹 * @param list */ public void createHafumanTree(ArrayList<TreeNode<Integer>> list){ while(list.size()>1){ Collections.sort(list); TreeNode<Integer> left =list.remove(0); TreeNode<Integer> right =list.remove(0); TreeNode<Integer> parent =new TreeNode<Integer>(data,left.weight+right.weight); parent.left=left; parent.right=right; left.parent=parent; right.parent=parent; list.add(parent); } if(!list.isEmpty()){ this.root=list.get(0); }else{ this.root=null; } }

那麼哈夫曼編碼怎麼獲取到?
這裡使用的方法是先搜尋樹,查詢到要編碼的節點位置,然後通過判斷父節點的分支方向並
使用棧來儲存結果
步驟:
1、通過搜尋樹找到節點(這裡使用廣度優先搜尋)。
2、迴圈判斷該節點父節點是否為空
3、如果該節點是左分支則入棧0,如果其是右分支則入棧1,向上移動,迴圈進入步驟二。

例如:查詢 71 節點的哈夫曼編碼

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

步驟二:判斷其父節點給null,則直接退出

最後然後遍歷棧,得到的就是哈夫曼編碼了

71的哈夫曼編碼是 00

具體程式碼如下:

    /**
     * 普通樹查詢
     * 廣度優先遍歷、當然還有前、中、後序遍歷樹(遞迴或者棧實現)
     * @param data
     * @return
     */
    public TreeNode<Integer> sreachNode(int data){
        TreeNode<Integer> result=null;
        if(null==root){
            return null;
            }else{
            LinkedList<TreeNode<Integer>> list =new LinkedList<>();
            list.addFirst(root);
            while(!list.isEmpty()&&result==null){
                TreeNode<Integer> node =list.pollFirst();
                if(node.data==data)
                    result=node;
                if(node.left!=null){
                    list.addLast(node.left);
                }
                if(node.right!=null){
                    list.addLast(node.right);
                }
            }
        }
        return result;
    }
    /**
     * 棧前序遍歷
     * @param node
     */
    public void preStackLook(TreeNode<Integer> node){
        if(null==node){
            return ;
        }else{
            Stack<TreeNode<Integer>> stack=new Stack<>();
            stack.push(node);
            while(!stack.isEmpty()){
                node=stack.pop();
                System.out.print(node.data +"   ");
                if(null!=node.right)
                    stack.push(node.right);
                if(null!=node.left)
                    stack.push(node.left);
            }
        }
    }
    /**
     * 遞迴前序遍歷
     * @param root
     */
    public void preOrderLook(TreeNode<Integer> root){
        if(null==root){
            return ;
        }
        System.out.print(root.data+"    ");
        preOrderLook(root.left);
        preOrderLook(root.right);
    }

    /**
     * 遞迴中序遍歷
     * @param root
     */
    public void midOrderLook(TreeNode<Integer> root){
        if(null==root){
            return ;
        }
        midOrderLook(root.left);
        System.out.print(root.data+"    ");
        midOrderLook(root.right);
    }

    /**
     * 遞迴前後序遍歷
     * @param root
     */
    public void breforeOrderLook(TreeNode<Integer> root){
        if(null==root){
            return ;
        }
        breforeOrderLook(root.left);
        breforeOrderLook(root.right);
        System.out.print(root.data+"    ");
    }
    /**
     * 獲取哈夫曼編碼
     * @param data
     * @return
     */
    public String getCode(Integer data){
        TreeNode<Integer> node =sreachNode(data);
        String result="";
        if(node==null)
            return null;
        else{
            Stack<Integer> stack =new Stack<>();
            while(node.parent!=null){
                if(node.parent.left==node){
                    stack.push(0);
                }else{
                    stack.push(1);
                }
                node=node.parent;
            }
            for(;!stack.isEmpty();){
                result+=stack.pop();
            }
        }
        return result;
    }

總結:以上就是哈夫曼樹的由來、建立、遍歷、獲取哈夫曼編碼等的過程了。比較簡單,記錄一下,方便以後檢視