1. 节点之间的路径长度:在树中从一个结点到另外一个结点所经历的分支,构成了这两个结点间的路径上的通过的分支数称为它的路径长度
2. 树的路径长度:从树的根节点到树中每一结点的路径长度之和。在结点数目相同的二叉树中,彻底二叉树的路径长度最短。
3. 结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数。
4. 结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积
5. 树的带权路径长度:定义为树中全部叶子结点的带权路径长度之和
6. 最优二叉树:从已给出的目标带权结点(单独的结点) 通过一种方式的组合造成一棵树.使树的权值最小.。最优二叉树是带权路径长度最短的二叉树。根据结点的个数,权值的不一样,最优二叉树的形状也各不相同。它们的共同点是:带权值的结点都是叶子结点。权值越小的结点,其到根结点的路径越长。html
public class HuffmanTreeNode implements Comparable<HuffmanTreeNode> { private int weight; private HuffmanTreeNode parent, left, right; private char character; public HuffmanTreeNode(HuffmanTreeNode parent, HuffmanTreeNode left, HuffmanTreeNode right, int weight, char character) { this.weight = weight; this.character = character; this.parent = parent; this.left = left; this.right = right; } @Override public int compareTo(HuffmanTreeNode huffmanTreeNode) { return this.weight - huffmanTreeNode.getWeight(); } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public HuffmanTreeNode getParent() { return parent; } public void setParent(HuffmanTreeNode parent) { this.parent = parent; } public HuffmanTreeNode getLeft() { return left; } public void setLeft(HuffmanTreeNode left) { this.left = left; } public HuffmanTreeNode getRight() { return right; } public void setRight(HuffmanTreeNode right) { this.right = right; } public char getCharacter() { return character; } public void setElement(char character) { this.character = character; } }
构造哈夫曼树是最重要的一步,由于它关系到编码的0/1序号是否正确以及简单
课堂上对于哈夫曼树的理解不是很明白,经过这篇博客有了更深的了解哈夫曼树(三)之 Java详解node
有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w一、w二、…、wn,哈夫曼树的构造规则为:git
构造哈夫曼树须要对权值进行排序,每次取出两个最小值并求和做为父节点组成一颗小树,如此重复直至只剩一棵树即为所求哈夫曼树
排序有多种方法,这里用的是堆排序:
按顺序每次取两个最小值,第一个做为左孩子(编码为0),第二个做为右孩子(编码为1)
而后求和,将其设为父节点
继续循环数组
具体过程如图所示:
以{5,6,7,8,15}为例,来构造一棵哈夫曼树:
ide
public class HuffmanTree { private HuffmanTreeNode mRoot; // 根结点 private String[] codes = new String[26];//用数组存放26个字母的几率(即权值) public HuffmanTree(HuffmanTreeNode[] array) { HuffmanTreeNode parent = null; ArrayHeap<HuffmanTreeNode> heap = new ArrayHeap(); for (int i=0;i<array.length;i++) { heap.addElement(array[i]);//将权值放入堆中 } for(int i=0; i<array.length-1; i++) { HuffmanTreeNode left = heap.removeMin(); //取第一个 最小节点做为左孩子 HuffmanTreeNode right = heap.removeMin();//取第二个 最小节点做为右孩子 parent = new HuffmanTreeNode(null,left,right,left.getWeight()+right.getWeight(),' ');//求左右孩子之和并存为哈夫曼节点 left.setParent(parent); right.setParent(parent);//将和设置为左右孩子的父节点 heap.addElement(parent);//将父节点添加到新的堆中(此时两个孩子已经被删除) } mRoot = parent;//循环结束,把最后一个父节点设置为根结点 } }
根据前面所说的哈夫曼编码规则
哈夫曼树的左孩子对应0,右孩子对应1
因此只要能判断根结点到叶子节点路径上的全部节点是左仍是右孩子便可写出对应编码this
可是存在的问题就是,此那个根节点开始写仍是从叶子节点写
由于最终的编码是从根结点开始写起的,可是从根节点开始写就没法判断哪条路径是通向叶子节点,判断会很麻烦
因此只要先将全部的叶子节点找出来存在数组里,而后向上去找
经过编码
if (node==node.getParent().getLeft()) stack.push(0); if (node==node.getParent().getRight()) stack.push(1);
来判断当前节点是左孩子仍是右孩子.net
而后输出栈里的0/1,即为正序的编码3d
public String[] getEncoding() { ArrayList<HuffmanTreeNode> arrayList = new ArrayList(); inOrder(mRoot,arrayList);//将叶子节点中序遍历存放在列表里 for (int i=0;i<arrayList.size();i++) { HuffmanTreeNode node = arrayList.get(i);//将每个列表中元素变成哈夫曼节点 String result =""; int x = node.getCharacter()-'a';//x为列表中元素转化为字母表中顺序的索引值 Stack stack = new Stack();//每一次栈里存放的都是某一个元素的编码值的逆序 while (node!=mRoot)//判断当前节点是左孩子仍是右孩子 { if (node==node.getParent().getLeft()) stack.push(0); if (node==node.getParent().getRight()) stack.push(1); node=node.getParent();//判断完当前节点后,节点向上移动 } while (!stack.isEmpty()) { result +=stack.pop(); } codes[x] = result;//result为每个字母的哈夫曼编码值,存放在对应的字母索引值x处 } return codes;//codes为存放每个字母编码值的数组 }
由于哈夫曼编码是变长的,也就是每个元素对应的0/1个数不同长,没法判断哪几位对应哪个元素
可是根据哈夫曼树原理,仍然能够经过一个一个的读取0/1来进行判断,由于一个0/1就对应了当前节点是左仍是右孩子
每读取一次,就判断当前节点有无孩子,而只要读取到叶子节点,即找到了一个字母元素code
//从文件读取 Scanner scan1 = new Scanner(file1); String string = scan1.nextLine();//string为原文编码后的全部内容 HuffmanTreeNode huffmanTreeNode = huffmanTree.getmRoot();//从根节点开始读取 //进行解码 String result2 = ""; for (int i = 0; i < string.length(); i++) { if (string .charAt(i) == '0') {//读取第一个数字判断是否为0(便是否为左孩子) if (huffmanTreeNode.getLeft() != null) {//判断当前节点是否有左孩子 huffmanTreeNode = huffmanTreeNode.getLeft(); } } else { if (string .charAt(i) == '1') { if (huffmanTreeNode.getRight() != null) { huffmanTreeNode = huffmanTreeNode.getRight(); } } } if (huffmanTreeNode.getLeft() == null && huffmanTreeNode.getRight() == null) {//判断当前节点是否为叶子节点 result2 += huffmanTreeNode.getCharacter();//是叶子节点即为找到了一个字母,将其加入到解码后的结果result2 huffmanTreeNode = huffmanTree.getmRoot();//找到一个字母,即从根结点开始继续循环找下一个字母 }//若是一次循环没找到,则继续循环直到找到叶子节点,数字读完即为循环结束 }
侯泽洋同窗统计字母出现几率的方法很是好!
从中受益不浅
他直接将每一个字母减掉‘ a ’,对应ASCII表中的差值即为当前字母对应26个字母中的位置索引
从而每个差值相等的字母都是相同的,且能知道这个字母是谁
Scanner scan = new Scanner(file); String s = scan.nextLine(); int[] array = new int[26]; for (int i = 0; i < array.length; i++) { array[i] = 0;//初始化 } for (int i = 0; i < s.length(); i++) { char x = Character.toLowerCase(s.charAt(i));//将大写字母转换成小写字母 array[x - 'a']++;//相同字母的位置索引上的数字自增 } System.out.println("打印各字母出现频率:"); for (int i = 0; i < array.length; i++) { System.out.println((char)('a'+i)+":"+(double) array[i] / s.length()); }