欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

哈弗曼树

程序员文章站 2022-07-14 19:06:30
...

https://www.jb51.net/article/90728.htm
https://blog.csdn.net/bruce_6/article/details/38656413
http://www.cnblogs.com/ssyfj/p/9472733.html

定义:

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

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

如,给定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.没有度为1的结点
2.n个叶子节点的哈夫曼树共有2n-1个结点

树的特点:度为2结点和叶结点的关系n2=n0-1
所以:当叶结点为n时,度为二的结点数为n-1
因为哈夫曼没有度为一的结点,所以一共在树中有2n-1个结点

3.哈夫曼树任意非叶结点的左右子树交换后还是哈夫曼树
4.对同一组权值{w1,w2,…,wn},是会存在不同结构的哈夫曼树


构造哈夫曼树

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

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

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

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

构造过程如下图:
哈弗曼树

例子2:

有一个字符串:aaaaaaaaaabbbbbaaaaaccccccccddddddfff

第一步,我们先统计各个字符出现的次数,称之为该字符的权值。a 15 ,b 5, c 8, d 6, f 3。

第二步,找去这里面权值最小的两个字符,b5和f3,构建节点。
哈弗曼树

然后将f3和b5去掉,现在是a15,c8,d6,fb8。

第三步,重复第二步,直到构建出只剩一个节点。
哈弗曼树

现在是dfb14,a15,c8。

哈弗曼树

最后,
哈弗曼树

构建的步骤

 1.统计字符串中字符以及字符的出现次数;

 2.根据第一步的结构,创建节点;

 3.对节点权值升序排序;

 4.取出权值最小的两个节点,生成一个新的父节点;

 5.删除权值最小的两个节点,将父节点存放到列表中;

 6.重复第四五步,直到剩下一个节点;

 7.将最后的一个节点赋给根节点。

代码实现

首先需要有个节点类来存放数据。

package huffman;
/**
 * 节点类
 * @author yuxiu
 *
 */
public class Node {
 public String code;// 节点的哈夫曼编码
 public int codeSize;// 节点哈夫曼编码的长度
 public String data;// 节点的数据
 public int count;// 节点的权值
 public Node lChild;
 public Node rChild;

 public Node() {
 }

 public Node(String data, int count) {
  this.data = data;
  this.count = count;
 }

 public Node(int count, Node lChild, Node rChild) {
  this.count = count;
  this.lChild = lChild;
  this.rChild = rChild;
 }

 public Node(String data, int count, Node lChild, Node rChild) {
  this.data = data;
  this.count = count;
  this.lChild = lChild;
  this.rChild = rChild;
 }
}

构造过程

package huffman;

import java.io.*;
import java.util.*;

public class Huffman {
 private String str;// 最初用于压缩的字符串
 private String newStr = "";// 哈夫曼编码连接成的字符串 
 private Node root;// 哈夫曼二叉树的根节点
 private boolean flag;// 最新的字符是否已经存在的标签
 private ArrayList<String> charList;// 存储不同字符的队列 相同字符存在同一位置
 private ArrayList<Node> NodeList;// 存储节点的队列

  15  16  /**
  * 构建哈夫曼树
  * 
  * @param str
  */
 public void creatHfmTree(String str) {
  this.str = str;
  charList = new ArrayList<String>();
  NodeList = new ArrayList<Node>();
  // 1.统计字符串中字符以及字符的出现次数
  // 基本思想是将一段无序的字符串如ababccdebed放到charList里,分别为aa,bbb,cc,dd,ee
  // 并且列表中字符串的长度就是对应的权值
  for (int i = 0; i < str.length(); i++) {
   char ch = str.charAt(i); // 从给定的字符串中取出字符
   flag = true;
   for (int j = 0; j < charList.size(); j++) {
    if (charList.get(j).charAt(0) == ch) {// 如果找到了同一字符
     String s = charList.get(j) + ch;
     charList.set(j, s);
     flag = false;
     break;
    }
   }
   if (flag) {
    charList.add(charList.size(), ch + "");
   }
  }
  // 2.根据第一步的结构,创建节点
  for (int i = 0; i < charList.size(); i++) {
   String data = charList.get(i).charAt(0) + ""; // 获取charList中每段字符串的首个字符
   int count = charList.get(i).length(); // 列表中字符串的长度就是对应的权值
   Node node = new Node(data, count); // 创建节点对象
   NodeList.add(i, node); // 加入到节点队列
  }

  // 3.对节点权值升序排序
  Sort(NodeList);
  while (NodeList.size() > 1) {// 当节点数目大于一时
   // 4.取出权值最小的两个节点,生成一个新的父节点
   // 5.删除权值最小的两个节点,将父节点存放到列表中
   Node left = NodeList.remove(0);
   Node right = NodeList.remove(0);
   int parentWeight = left.count + right.count;// 父节点权值等于子节点权值之和
   Node parent = new Node(parentWeight, left, right);
   NodeList.add(0, parent); // 将父节点置于首位

  }
  // 6.重复第四五步,就是那个while循环
  // 7.将最后的一个节点赋给根节点
  root = NodeList.get(0);
 }
 /**
  * 升序排序
  * 
  * @param nodelist
  */
 public void Sort(ArrayList<Node> nodelist) {
  for (int i = 0; i < nodelist.size() - 1; i++) {
   for (int j = i + 1; j < nodelist.size(); j++) {
    Node temp;
    if (nodelist.get(i).count > nodelist.get(j).count) {
     temp = nodelist.get(i);
     nodelist.set(i, nodelist.get(j));
     nodelist.set(j, temp);
    }

   }
  }

 }

 /**
  * 遍历
  * 
  * @param node
  *   节点
  */
 public void output(Node node) {
  if (node.lChild != null) {
   output(node.lChild);
  }
  System.out.print(node.count + " "); // 中序遍历
  if (node.rChild != null) {
   output(node.rChild);
  }
 }

 public void output() {
  output(root);
 }
/**
  * 主方法
  * 
  * @param args
  */
 public static void main(String[] args) {
  Huffman huff = new Huffman();//创建哈弗曼对象
  huff.creatHfmTree("sdfassvvdfgsfdfsdfs");//构造树
 }