哈夫曼树(最优二叉树)及其Java实现

一、定义

一些定义:

  • 节点之间的路径长度:在树中从一个结点到另一个结点所经历的分支,构成了这两个结点间的路径上的经过的分支数称为它的路径长度
  • 树的路径长度:从树的根节点到树中每一结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。
  • 结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数。
  • 结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。
  • 树的带权路径长度(Weighted Path Length of Tree:WPL):定义为树中所有叶子结点的带权路径长度之和

如下面的二叉树,叶子节点的权值分别为5、6、2、4、7,的带权路径长度计算:

  • 最优二叉树:从已给出的目标带权结点(单独的结点) 经过一种方式的组合形成一棵树.使树的权值最小.。最优二叉树是带权路径长度最短的二叉树。根据结点的个数,权值的不同,最优二叉树的形状也各不相同。它们的共同点是:带权值的结点都是叶子结点。权值越小的结点,其到根结点的路径越长。

如,给定4个叶子结点a,b,c和d,分别带权7,5,2和4。构造如下图所示的三棵二叉树(还有许多棵),它们的带权路径长度分别为:

(a)WPL=7*2+5*2+2*2+4*2=36

(b)WPL=7*3+5*3+2*1+4*2=46

(c)WPL=7*1+5*2+2*3+4*3=35

其中(c)树的WPL最小,可以验证,它就是哈夫曼树。

注意:

① 叶子上的权值均相同时,完全二叉树一定是最优二叉树,否则完全二叉树不一定是最优二叉树。

② 最优二叉树中,权越大的叶子离根越近。

③ 最优二叉树的形态不唯一,WPL最小。

二、构造哈夫曼树

1) 根据给定的n个权值{w1, w2, w3, w4......wn}构成n棵二叉树的森林 F={T1 , T2 , T3.....Tn},其中每棵二叉树只有一个权值为wi 的根节点,其左右子树都为空;

2) 在森林F中选择两棵根节点的权值最小的二叉树,作为一棵新的二叉树的左右子树,且令新的二叉树的根节点的权值为其左右子树的权值和;

3)从F中删除被选中的那两棵子树,并且把构成的新的二叉树加到F森林中;

4)重复2 ,3 操作,直到森林只含有一棵二叉树为止,此时得到的这棵二叉树就是哈夫曼树。

构造过程如下图:

三、Java实现

对指定节点创建哈夫曼树:

package com.liuhao.DataStructures;

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

public class HuffmanTree {

	public static class Node<E> {
		E data;
		double weight;
		Node leftChild;
		Node rightChild;

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

		public String toString() {
			return "Node[data=" + data + ", weight=" + weight + "]";
		}
	}

	public static void main(String[] args) {
		List<Node> nodes = new ArrayList<Node>();

		nodes.add(new Node("A", 40.0));
		nodes.add(new Node("B", 8.0));
		nodes.add(new Node("C", 10.0));
		nodes.add(new Node("D", 30.0));
		nodes.add(new Node("E", 10.0));
		nodes.add(new Node("F", 2.0));

		Node root = HuffmanTree.createTree(nodes);

		System.out.println(breadthFirst(root));

	}

	/**
	 * 构造哈夫曼树
	 *
	 * @param nodes
	 *            节点集合
	 * @return 构造出来的哈夫曼树的根节点
	 */
	private static Node createTree(List<Node> nodes) {
		// 只要nodes数组中还有2个以上的节点
		while (nodes.size() > 1) {
			quickSort(nodes);
			//获取权值最小的两个节点
			Node left = nodes.get(nodes.size()-1);
			Node right = nodes.get(nodes.size()-2);

			//生成新节点,新节点的权值为两个子节点的权值之和
			Node parent = new Node(null, left.weight + right.weight);

			//让新节点作为两个权值最小节点的父节点
			parent.leftChild = left;
			parent.rightChild = right;

			//删除权值最小的两个节点
			nodes.remove(nodes.size()-1);
			nodes.remove(nodes.size()-1);

			//将新节点加入到集合中
			nodes.add(parent);
		}

		return nodes.get(0);
	}

	/**
	 * 将指定集合中的i和j索引处的元素交换
	 *
	 * @param nodes
	 * @param i
	 * @param j
	 */
	private static void swap(List<Node> nodes, int i, int j) {
		Node tmp;
		tmp = nodes.get(i);
		nodes.set(i, nodes.get(j));
		nodes.set(j, tmp);
	}

	/**
	 * 实现快速排序算法,用于对节点进行排序
	 *
	 * @param nodes
	 * @param start
	 * @param end
	 */
	private static void subSort(List<Node> nodes, int start, int end) {
		if (start < end) {
			// 以第一个元素作为分界值
			Node base = nodes.get(start);
			// i从左边搜索,搜索大于分界值的元素的索引
			int i = start;
			// j从右边开始搜索,搜索小于分界值的元素的索引
			int j = end + 1;
			while (true) {
				// 找到大于分界值的元素的索引,或者i已经到了end处
				while (i < end && nodes.get(++i).weight >= base.weight)
					;
				// 找到小于分界值的元素的索引,或者j已经到了start处
				while (j > start && nodes.get(--j).weight <= base.weight)
					;

				if (i < j) {
					swap(nodes, i, j);
				} else {
					break;
				}
			}

			swap(nodes, start, j);

			//递归左边子序列
			subSort(nodes, start, j - 1);
			//递归右边子序列
			subSort(nodes, j + 1, end);
		}
	}

	public static void quickSort(List<Node> nodes){
		subSort(nodes, 0, nodes.size()-1);
	}

	//广度优先遍历
	public static List<Node> breadthFirst(Node root){
		Queue<Node> queue = new ArrayDeque<Node>();
		List<Node> list = new ArrayList<Node>();

		if(root!=null){
			//将根元素加入“队列”
			queue.offer(root);
		}

		while(!queue.isEmpty()){
			//将该队列的“队尾”元素加入到list中
			list.add(queue.peek());
			Node p = queue.poll();

			//如果左子节点不为null,将它加入到队列
			if(p.leftChild != null){
				queue.offer(p.leftChild);
			}

			//如果右子节点不为null,将它加入到队列
			if(p.rightChild != null){
				queue.offer(p.rightChild);
			}
		}

		return list;
	}
}

以上代码中的关键步骤包括:

(1)对list集合中所有节点进行排序;

(2)找出list集合中权值最小的两个节点;

(3)以权值最小的两个节点作为子节点创建新节点;

(4)从list集合中删除权值最小的两个节点,将新节点添加到list集合中

程序采用循环不断地执行上面的步骤,直到list集合中只剩下一个节点,最后剩下的这个节点就是哈夫曼树的根节点

四、哈夫曼编码

根据哈夫曼树可以解决报文编码的问题。假设需要把一个字符串,如“abcdabcaba”进行编码,将它转换为唯一的二进制码,但是要求转换出来的二进制码的长度最小。

假设每个字符在字符串中出现频率为W,其编码长度为L,编码字符n个,则编码后二进制码的总长度为W1L1+W2L2+…+WnLn,这恰好是哈夫曼树的处理原则。因此可以采用哈夫曼树的构造原理进行二进制编码,从而使得电文长度最短。

对于“abcdabcaba”,共有a、b、c、d4个字符,出现次数分别为4、3、2、1,相当于它们的权值,将a、b、c、d以出现次数为权值构造哈夫曼树,得到下左图的结果。

从哈夫曼树根节点开始,对左子树分配代码“0”,对右子树分配“1”,一直到达叶子节点。然后,将从树根沿着每条路径到达叶子节点的代码排列起来,便得到每个叶子节点的哈夫曼编码,如下右图。

从图中可以看出,a、b、c、d对应的编码分别为0、10、110、111,然后将字符串“abcdabcaba”转换为对应的二进制码就是0101101110101100100,长度仅为19。这也就是最短二进制编码,也称为哈夫曼编码。

哈夫曼树(最优二叉树)及其Java实现

时间: 2024-08-27 07:57:28

哈夫曼树(最优二叉树)及其Java实现的相关文章

由二叉树构造赫夫曼树

赫夫曼树: 假设有n个权值{w1,w2,w3....},试构造一棵具有n个叶子节点的二叉树,每个叶子节点带权为wi,则其中带权路径长度最小的二叉树称为最优二叉树或者叫赫夫曼树. 构造赫夫曼树: 假设有n个权值,则构造出的赫夫曼树有n个叶子节点,n个权值分别设置为w1,w2,....wn,则赫夫曼树的构造规则为: 1.将w1,w2...看成是有n棵树的森林: 2.在森林中选择两个根节点的权值最小的树合并,作为一棵新树的左右子树,且新树的根节点权值为其左右子树根节点权值之和: 3.从森林中删除选取的

哈夫曼树(三)之 Java详解

前面分别通过C和C++实现了哈夫曼树,本章给出哈夫曼树的java版本. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 哈夫曼树的介绍 Huffman Tree,中文名是哈夫曼树或霍夫曼树,它是最优二叉树. 定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树. 这

详细了解哈夫曼树和背包问题

写在前面 最近在疯狂复习数据结构和算法,虽然看完了一部完整的视频.但是转眼看看自己手中的<剑指Offer>里面还是不是很清楚...而且最近也突然觉得自己知识和别人比起来就是一个渣渣.各种被人家吊打... 这两个算法一个(哈夫曼树)是看最近视频动手实践的,一个(背包问题)是前段时间一个面试里面的题目,当时不知道这是一个系类的问题,昨天和大神聊完天之后才明白.所以乘着短暂的热情还在就记录下来先从哈夫曼树开始!! 1.哈夫曼树(实现基本的编码解码) 简单定义: 给定n个权值作为n个叶子结点,构造一棵

数据结构:哈夫曼树和哈夫曼编码

哈夫曼树 哈夫曼树是一种最优二叉树,其定义是:给定n个权值作为n个叶子节点,构造一棵二叉树,若树的带权路径长度达到最小,这样的树就达到最优二叉树,也就是哈夫曼树,示例图如下: 基本概念 深入学习哈夫曼树前,先了解一下基本概念,并以上面的哈夫曼树图为例 路径:树中一个结点到另一个结点之间的分支序列构成两个结点间的路径. 路径长度:路径中分支的数目,从根结点到第L层结点的路径长度为L-1.例如100和80的路径长度为1,50和30的路径长度为2. 结点的权:树中结点的数值,例如100,50那些. 结

最优二叉树(霍夫曼树)分析

 1.树的路径长度     树的路径长度是从树根到树中每一结点的路径长度之和.在结点数目相同的二叉树中,完全二叉树的路径长度最短. 2.树的带权路径长度(Weighted Path Length of Tree,简记为WPL) 结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数. 结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积. 树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和,通常记为: 其中: n

javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径. ② 路径长度:结点路径上的分支数目称为路径长度. ③ 树的路径长度:从树根到每一个结点的路径长度之和. 以下图为例: A到F :结点路径 AEF : 路径长度(即边的数目) 2 : 树的路径长度:3*1+5*2+2*3=19: ④ 结点的带权路径长度:从该结点的到树的根结

哈夫曼树(最优二叉树)及哈夫曼算法

哈夫曼树 在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUFFMAN)树和哈夫曼编码.哈夫曼编码是哈夫曼树的一个应用.哈夫曼编码应用广泛,如JPEG中就应用了哈夫曼编码. 首先介绍什么是哈夫曼树.哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树.所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数).树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权

哈夫曼树(最优二叉树)的创建

哈夫曼树是带权路径最小的一种特殊二叉树,所以也称最优二叉树. 在这里不讨论基本概念如怎样计算路径等,而仅仅着重于树的创建,详细过程让我们举例而言. 其主要的原理为:将全部节点一開始都视为森林.每次从森林中选取两个根节点权值最小的树合并为一棵新树,新树的根节点大小为两个子节点大小的和,并将这棵新树又一次增加到森林中. 如此一来每一轮操作都能够简化为两个基本操作:合并两棵树.插入新树,直到森林中仅仅剩下一棵树,即是哈夫曼树. 以7个节点的权值分别为 1 3 7 9 12 18 25而言 创建的第一步

Huffman tree(赫夫曼树、霍夫曼树、哈夫曼树、最优二叉树)

flyfish 2015-8-1 Huffman tree因为翻译不同所以有其他的名字 赫夫曼树.霍夫曼树.哈夫曼树 定义引用自严蔚敏<数据结构> 路径 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径. 路径长度 路径上的分支数目称作路径长度. 树的路径长度 树的路径长度就是从根节点到每一结点的路径长度之和. 结点的带权路径长度 结点的带权路径长度就是从该结点到根节点之间的路径长度与结点上权的乘积. 树的带权路径长度 树的带权路径长度就是树中所有叶子结点的带权路径长度之和,通常记做