哈夫曼树及其实现

首先了解一下什么是哈夫曼树:

给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,即权值较大的结点离根较近。

哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明哈夫曼树的WPL是最小的。

下面给出关于哈夫曼树的一些基本概念:

1、路径和路径长度

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

2、结点的权及带权路径长度

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

3、树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为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 gxu.wjb.tree;

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

/**
 * 哈夫曼树的构建
 * @version 创建时间:2019-8-9 下午2:51:02
 * 
 * 
 */
public class HuffmanTree {
	public static class Node<E> {
		E data;
		double weight;
		Node<E> leftChild;
		Node<E> rightChild;

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

	public static void main(String[] args) {
		List<Node<String>> nodes = new ArrayList<Node<String>>();
		nodes.add(new Node<String>("A", 40.0));
		nodes.add(new Node<String>("B", 8.0));
		nodes.add(new Node<String>("C", 10.0));
		nodes.add(new Node<String>("D", 30.0));
		nodes.add(new Node<String>("E", 10.0));
		nodes.add(new Node<String>("F", 2.0));

		Node<String> root = HuffmanTree.createTree(nodes);
		breadthFirst(root);
	}

	public static Node<String> createTree(List<Node<String>> nodes) {
		// 只要nodes列表中还有2个及以上的节点
		while (nodes.size() > 1) {
			quickSort(nodes, 0, nodes.size() - 1);
			// 获取权值最小的两个点
			Node<String> left = nodes.get(0);
			Node<String> right = nodes.get(1);
			// 生成新节点,新节点的权值为两个子节点权值之和
			Node<String> parent = new Node<String>(null, left.weight
					+ right.weight);
			// 让新节点作为两个最小节点的父节点
			parent.leftChild = left;
			parent.rightChild = right;

			// 删除两个最小节点
			nodes.remove(0);
			nodes.remove(0);

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

		// 剩余一个节点的时候返回即可
		return nodes.get(0);
	}

	/**
	 * 实现快速排序算法,该快排是基于荷兰国旗的思想进行改进后的实现,用于对节点进行排序
	 */
	public static void quickSort(List<Node<String>> nodes, int L, int R) {
		if (L < R) {
			int[] p = getProcess(nodes, nodes.get(L), L, R);
			quickSort(nodes, L, p[0] - 1);
			quickSort(nodes, p[1], R);
		}
	}

	private static int[] getProcess(List<Node<String>> nodes,
			Node<String> base, int L, int R) {
		int less = L - 1;
		int more = R + 1;
		int cur = L;
		while (cur < more) {
			if (nodes.get(cur).weight < base.weight) {
				swap(nodes, ++less, cur++);
			} else if (nodes.get(cur).weight == base.weight) {
				cur++;
			} else {
				swap(nodes, --more, cur++);
			}
		}

		return new int[] { less + 1, more };
	}

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

	// 广度优先遍历,也就是按层次遍历
	public static void breadthFirst(Node<String> head) {
		if (head == null) {
			return;
		}
		Queue<Node<String>> queue = new LinkedList<Node<String>>();
		queue.offer(head);
		while (!queue.isEmpty()) {
			head = queue.poll();
			System.out.println(head.data);
			if (head.leftChild != null) {
				queue.offer(head.leftChild);
			}
			if (head.rightChild != null) {
				queue.offer(head.rightChild);
			}
		}
	}
}

哈夫曼编码

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

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

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

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

                              0image

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

发布了61 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_33204444/article/details/98854199