哈夫曼树Huffman Tree详解与java实现

哈夫曼树

  • 树的一些基本概念
  • 哈夫曼树
    • 哈夫曼树简介
    • 构造哈夫曼树的通用方法
    • java实现

树的一些基本概念

路径:在一棵树中,从一个结点到另一个结点所经过的所有结点,被我们称为两个结点间的路径。
路径长度:在一棵树中从一个结点到另一个结点所经过的“边”的数量为这两个结点之间的路径长度。
 结点的带权路径长度:树的每个结点都可以拥有自己的“权重”(weight)。结点的带权路径长度就是指树的根结点到该结点的路径长度,和该结点权重乘积
 树的带权路径长度:就是指一棵树中所有叶子结点的带权路径长度之和,也被简称为WPL
 示例:
 如下图二叉树中,从根节点A到叶子结点H的路径是A,B,D,H。
 共经历了三条边A->B,B->D,D->H,因此路径为3。
 如图示,结点H的权重是3,则结点H的带权路径长度是3X3 = 9。
 树的路径长度为 3X3 + 6X3 + 1X2 + 4X2 + 8X2 = 53 。
 哈夫曼树Huffman Tree详解与java实现_第1张图片

哈夫曼树

哈夫曼树简介

哈夫曼树(Huffman Tree)是在叶子结点和权重确定的情况下,带权路径长度最小的二叉树,也被称为最优二叉树
 举个例子,给定权重分别为1,3,4,6,8的叶子结点,应该怎么构建二叉树使得其带权路径长度(叶子节点的路径与权重乘积和)最小?
 原则上,让权重小的叶子节点路径长(远离树根),权重大的叶子结点路径短(靠近树根)。如下图,左侧的这棵树就是哈夫曼树,它的WPL是46,小于之前例子中的53。
 哈夫曼树Huffman Tree详解与java实现_第2张图片

注意:同样的叶子结点构成的哈夫曼树可能不止一棵,比如下面这几棵树都是哈夫曼树。哈夫曼树Huffman Tree详解与java实现_第3张图片

构造哈夫曼树的通用方法

通用方法总结:将叶子节点的权重从小到大排列,因为越小路径要越长,那么我们就从权重小的结点开始,按照规则两两配对自底向上的开始构造一个二叉树。主要操作只有两步,① 选择当前权重最小的两个结点,生成新的父结点;② 不考虑这两个已配对的子节点,将父结点的权重(俩子结点之和)加入队列,再选出当前权重最小两个结点重复步骤一。具体可看下面例子。

 (给定结点和权重)示例:假设有6个叶子结点,权重依次是2,3,7,9,18,25,如何构建一颗哈夫曼树,也就是带权路径长度最小的树呢?
哈夫曼树Huffman Tree详解与java实现_第4张图片

  1. 构建森林
    我们把每一个叶子结点,都当做一棵独立的树(只有根结点的树),这样就形成了一个森林。如下图,右侧是叶子结点的森林,左侧是一个辅助队列,按照权值从小到大存储了所有叶子结点。至于辅助队列的作用,我们后续将会看到。哈夫曼树Huffman Tree详解与java实现_第5张图片
  2. 选择当前权重最小的两个结点,生成新的父结点
    借助辅助队列,我们可以找到权值最小的结点2和3,并根据这两个结点生成一个新的父结点,父节点的权值是这两个结点权值之和。哈夫曼树Huffman Tree详解与java实现_第6张图片
  3. 从队列中移除上一步选择的两个最小结点,把新的父节点加入队列
    也就是从队列中删除2和3,插入5,并且仍然保持队列的升序:
    哈夫曼树Huffman Tree详解与java实现_第7张图片
  4. 选择当前权值最小的两个结点,生成新的父结点
    这是对第二步的重复操作。当前队列中权值最小的结点是5和7,生成新的父结点权值是5+7=12:
    哈夫曼树Huffman Tree详解与java实现_第8张图片
  5. 从队列中移除上一步选择的两个最小结点,把新的父节点加入队列
    这是对第三步的重复操作,也就是从队列中删除5和7,插入12,并且仍然保持队列的升序:哈夫曼树Huffman Tree详解与java实现_第9张图片
    之后循环重复第二步和第三步,直至队列仅剩最后一个结点(即根节点)为止。
  6. 选择当前权值最小的两个结点,生成新的父节点
    重复第二步,当前队列中权值最小的结点是9和12,生成新的父结点权值是9+12=21:
    哈夫曼树Huffman Tree详解与java实现_第10张图片
  7. 从队列中移除上一步选择的两个最小结点,把新的父节点加入队列
    重复第三步:从队列中删除9和12,插入21,并且仍然保持队列的升序:
    哈夫曼树Huffman Tree详解与java实现_第11张图片
  8. 选择当前权值最小的两个结点,生成新的父结点
    重复第二步:当前队列中权值最小的结点是18和21,生成新的父结点权值是18+21=39:
    哈夫曼树Huffman Tree详解与java实现_第12张图片
  9. 从队列中移除上一步选择的两个最小结点,把新的父节点加入队列
    重复第三步:从队列中删除18和21,插入39,并且仍然保持队列的升序:
    哈夫曼树Huffman Tree详解与java实现_第13张图片
  10. 选择当前权值最小的两个结点,生成新的父结点
    重复第二步:当前队列中权值最小的结点是25和39,生成新的父结点权值是25+39=64:
    哈夫曼树Huffman Tree详解与java实现_第14张图片
  11. 从队列中移除上一步选择的两个最小结点,把新的父节点加入队列
    重复第三步:从队列中删除25和39,插入64:
    哈夫曼树Huffman Tree详解与java实现_第15张图片
    此时,队列中仅有一个结点,说明整个森林已经合并成了一颗树,而这棵树就是我们想要的哈夫曼树。

java实现

private Node root;
private Node[] nodes;

//构建哈夫曼树
public void createHuffman(int[] weights){
	//创建一个优先队列,用来辅助构建哈夫曼树
	Queue<Node> nodeQueue = new PriorityQueue<>();
	nodes = new Node[weights.length]//构建森林,往优先队列里存储叶子结点的权重
	for(int i = 0; i < weights; i++){
		nodes[i] = new Node(weights[i]);//weight[]数组存储叶子结点(数据类型为结点,包含权重信息),默认按从小到大排列
		nodeQueue.add(node[i]);
	}
	//主循环,当结点队列只剩一个结点时结束
	while(nodeQueue.size()>1){
		//从结点队列选择权重最小的两个结点,当树的左右结点
		Node left = nodeQueue.poll();
		Node right = nodeQueue.poll();
		//创建新结点作为两结点的父结点
		Node parent = new Node(left.weight + right.weight,left,right);
		nodeQueue.add(parent);
	}
	root = nodeQueue.poll();//当结点队列只剩最后一个结点时,这个结点就是根节点。
} 

//按照前序遍历输出
public void output(Node head){
	if(head == null){
		return;
	}
	System.out.println(height.weight);
	output(head.lChild);
	output(head.rChild);//递归
}
public static class Node implements Comparable<Node>{
	int weight;
	Node lChild;
	Node rChild;
	
	public Node(int weight){
		this.weight = weight;
	}
	public Node(int weight, Node lChild, Node rChild){
		this.weight = weight;
		this.lChild = lChild;
		this.rChild = rChild;
	}
	@Overide
	pubic int compareTo(Node o){
		return new Integer(this.weight).compareTo(new Integer(o.weight));//排序
	}
}

public static void main(String[] args){
	int[] weight = {2,3,7,9,18,25};
	HuffmanTree huffmanTree = new HuffmanTree();
	huffmanTree.createHuffman(weights);
	huffmanTree.output(huffmanTree.root);
}

转载自:漫画:什么是 “哈夫曼树” ?— 算法与数据结构

你可能感兴趣的:(#,数据结构,数据结构与算法)