浅谈哈夫曼树的构建、遍历、编码

最近研究二叉树,比较经典的树就是哈夫曼树了,所以研究一下它的构建以及哈夫曼编码,恶补一下数据结构的知识。

有一段密文:aabbccabcacb,解析为电码传输,只能为0、1来表示
例如
a 0
b 1
c 01
d 10
… …
那么aabc….可以表示为00101,但是在解析的时候发现0 01 10可以出现混乱,001可以解析为 ac 或者 aab,这样就会导致数据不唯一。因此可以用二叉树来保证数据唯一。
浅谈哈夫曼树的构建、遍历、编码_第1张图片

左边用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+…
那么如何进行优化,是这个编码的长度最短?可以通过调整数节点的位置,例如下:

浅谈哈夫曼树的构建、遍历、编码_第2张图片

左边用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只剩下一个元素。

浅谈哈夫曼树的构建、遍历、编码_第3张图片
浅谈哈夫曼树的构建、遍历、编码_第4张图片
浅谈哈夫曼树的构建、遍历、编码_第5张图片
浅谈哈夫曼树的构建、遍历、编码_第6张图片
浅谈哈夫曼树的构建、遍历、编码_第7张图片
浅谈哈夫曼树的构建、遍历、编码_第8张图片

结束循环
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 
 */
class TreeNode<E> implements Comparable<TreeNode<E>>{
    E data;
    int weight;
    TreeNodeleft;
    TreeNoderight;
    TreeNode 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> list){
    while(list.size()>1){
        Collections.sort(list);
        TreeNode left =list.remove(0);
        TreeNode right =list.remove(0);
        TreeNode parent =new TreeNode(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 节点的哈夫曼编码

浅谈哈夫曼树的构建、遍历、编码_第9张图片
浅谈哈夫曼树的构建、遍历、编码_第10张图片
浅谈哈夫曼树的构建、遍历、编码_第11张图片

步骤二:判断其父节点给null,则直接退出

最后然后遍历栈,得到的就是哈夫曼编码了

71的哈夫曼编码是 00

具体代码如下:

    /**
     * 普通树查找
     * 广度优先遍历、当然还有前、中、后序遍历树(递归或者栈实现)
     * @param data
     * @return
     */
    public TreeNode sreachNode(int data){
        TreeNode result=null;
        if(null==root){
            return null;
            }else{
            LinkedList> list =new LinkedList<>();
            list.addFirst(root);
            while(!list.isEmpty()&&result==null){
                TreeNode 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 node){
        if(null==node){
            return ;
        }else{
            Stack> 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 root){
        if(null==root){
            return ;
        }
        System.out.print(root.data+"    ");
        preOrderLook(root.left);
        preOrderLook(root.right);
    }

    /**
     * 递归中序遍历
     * @param root
     */
    public void midOrderLook(TreeNode root){
        if(null==root){
            return ;
        }
        midOrderLook(root.left);
        System.out.print(root.data+"    ");
        midOrderLook(root.right);
    }

    /**
     * 递归前后序遍历
     * @param root
     */
    public void breforeOrderLook(TreeNode 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 node =sreachNode(data);
        String result="";
        if(node==null)
            return null;
        else{
            Stack 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;
    }

总结:以上就是哈夫曼树的由来、创建、遍历、获取哈夫曼编码等的过程了。比较简单,记录一下,方便以后查看

你可能感兴趣的:(数据结构)