【大话数据结构&算法】哈夫曼树

哈夫曼树又称为最优二叉树

1、路径和路径长度

在一棵树中,从一个节点往下可以达到的孩子或者子孙节点之间的通路称为路径。通路中分支的数目称为路径长度。若规定根节点的层数为1,则从根节点
到第L层节点的路径长度为L-1.

2、节点的权和带权路径长度

若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

3、树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。


4、哈夫曼树的构造:

假设有n个权值,则构造出的哈夫曼树有N个叶子节点。n个权值分别设为w1、w2……wn,则哈夫曼树的构造规则为:

1、将w1、w2……wn看成是有n棵树的森林(每棵树仅有一个节点);
2、在森林中选出两个根节点权值最小的树合并,作为一棵新树的左、右子树,且新树的根节点权值为其左、右子树根节点权值之和;
3、从森林中删除选取的两棵树,并将新树加入森林;
4、重复2、3步,直到森林中只剩一棵树为止,该树即为所求的的哈夫曼树。


5、基本性质:

具有n个叶子节点的哈夫曼树,一共需要2n-1个叶子节点。


6、java实现构造哈夫曼树以及对哈夫曼树的广度优先遍历:

Node.java


/**
 * 哈夫曼树的结点类
 * @author lmb
 *
 */
public class Node<T> implements Comparable<Node<T>> {

    private T data;
    private double weight;
    private Node<T> left;
    private Node<T> right;

    public Node(T data, double weight) {
        super();
        this.data = data;
        this.weight = weight;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public double getWeight() {
        return weight;
    }

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

    public Node<T> getLeft() {
        return left;
    }

    public void setLeft(Node<T> left) {
        this.left = left;
    }

    public Node<T> getRight() {
        return right;
    }

    public void setRight(Node<T> right) {
        this.right = right;
    }

    public String toString(){
        return "data : " + this.data + "; weight : " + this.weight;
    }
    @Override
    public int compareTo(Node<T> other) {
        if (other.getWeight() > this.getWeight()) {
            return 1;
        }
        if (other.getWeight() < this.getWeight()) {
            return -1;
        }
        return 0;
    }

}

HuffmanTree.java

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;

import edu.emory.mathcs.backport.java.util.Collections;

public class HuffmanTree<T> {

    public static void main(String[] args){
        List<Node<String>> list = new ArrayList<Node<String>>();
        list.add(new Node<String>("a",9));
        list.add(new Node<String>("b",5));
        list.add(new Node<String>("c",3));
        list.add(new Node<String>("d",7));
        list.add(new Node<String>("e",8));
        list.add(new Node<String>("f",6));
        //构造一棵哈夫曼树
        Node<String> root = createTree(list);
        //广度优先遍历刚刚构造的哈夫曼树
        List<Node<String>> nodes = breadth(root);
        for (int i = 0; i < nodes.size(); i++) {
            System.out.println(nodes.get(i));
        }
    }

    //创建哈夫曼树
    public static <T> Node<T> createTree(List<Node<T>> nodes){
        while(nodes.size() > 1){
            Collections.sort(nodes);
            Node<T> left = nodes.get(nodes.size() - 1);//左节点
            Node<T> right = nodes.get(nodes.size() - 2);//右节点
            //父节点
            Node<T> parent = new Node<T>(null,left.getWeight() + right.getWeight());
            //利用左右节点构造父节点
            parent.setLeft(left);
            parent.setRight(right);
            //从森林中删除所选的两棵树,并将新树加入森林
            nodes.remove(left);
            nodes.remove(right);
            nodes.add(parent);          
        }
        return nodes.get(0);
    }


    public static <T> List<Node<T>> breadth(Node<T> root){
        List<Node<T>> list = new ArrayList<Node<T>>();
        Queue<Node<T>> queue = new ArrayDeque<Node<T>>();
        //如果根节点不为空,将根节点加入队列
        if (root != null) {
            queue.offer(root);
        }
        while(!queue.isEmpty()){
            //将队列前端元素节点加入list中(使用但不移出)
            list.add(queue.peek());
            //获取并移出队列中的元素节点
            Node<T> node = queue.poll();
            //如果节点的左子树存在,将其加入队列
            if (node.getLeft() != null) {
                queue.offer(node.getLeft());
            }
            //如果节点的右子树存在,将其加入队列
            if (node.getRight() != null) {
                queue.offer(node.getRight());
            }
        }
        return list;
    }

}

运行结果:

data : null; weight : 38.0
data : null; weight : 16.0
data : null; weight : 22.0
data : null; weight : 8.0
data : e; weight : 8.0
data : a; weight : 9.0
data : null; weight : 13.0
data : c; weight : 3.0
data : b; weight : 5.0
data : f; weight : 6.0


附录:java.util.Queue用法

队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。

在java5中新增加了java.util.Queue接口,用以支持队列的常见操作。该接口扩展了java.util.Collection接口。

Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优点是通过返回a.u值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。 如果要使用前端而不移出该元素,使用element()或者peek()方法。

值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值