赫夫蔓编码
问题:形如:abbcddadaa,这样的字符串,每个字符(a,b,c,d)用0和1组合(如0,01,10,111,001),怎么让字符串整体编码后最短.并且没有歧异.
假如a:0 b:1 c:01 d:10,这样是有歧异的.01可以看作c ,也可以看作ab.
用空格来隔开每个字符,是个伪操作。空格也是一个符号。这样只能用1表示分割。而只有一个0来表示符号了。可以证明没有二叉树好。
联想到二叉树,假如每个根结点是0和1.那么每个叶子都不会有歧义.
不同型态的二叉树可供的选择有0,10,110,111. 或 00,01,10,11等等组合.
如何使‘abbcddadaa’用01,编码后最短呢?当然是 每个字符对应的编码的长度乘出现的次数就可以了。
解答:1,无歧义:考虑树的叶子,任何叶子走过的路,每步都是和其他叶子是不同的。也就是给出一个叶子的编码,是不会有任何叶子和它的子编码一样。也就是无歧义。
2. 字符串最短,就是每个字符对应的编码的长度乘出现的次数之和。
而这就是赫夫蔓树
赫夫蔓树定义:如果每个叶子是有权重的. 那么所有叶子到根的距离乘权重之和(wpl)最小的树,就是赫夫蔓树.
构建赫夫蔓树:选择2个偏僻的字符,也就是权重最低,组成2叉树,删除 2个叶子,加入到剩下的字符中,再选择2个最偏僻的2个叶子,一直循环,以至所有字符都表示为了叶子.就是赫夫蔓树.
让他们的根做为一个新的叶子,权重为2叶之和,
为什么,公式证明是归纳+反证法。有点晦涩。
说人话:
1.假设只有2个叶子,那么2个节点立马组合成一个单节点二叉树是最小树。
2.如果有颗树是最小树, 假如某个叶子Z分裂出了2个叶子,并且这2个叶子x,y的值 x+y=z ,且 x,y是新树所有叶子中比值最小。
3.我们要做的就是证明第二步操作生成的树还是最小树。
4。如果证明出来了。那么可以看出,上面的分裂操作和赫夫曼的堆积操作本质是一致的,只是方向刚好相反。也就证明了赫夫曼是正确的。
5.要证明首先有一个定理:给定的节点,组成的最小树中,肯定有个树,他的最深的叶子是2个最小值组成的。这个定理用反证法很好处理,可以优先证明这个定理。
6.现在用反证法证明第二步操作生成的树还是最小树。
T1:第二步中未分裂树
T2:第二步中分裂出x,y的树
T2':假设是分裂出x,y的最优树
T1':假设是T2'的未分裂之前的树。
反证的思路回顾下,根据已知条件,假设一个结果,推导出和已知条件矛盾,那么假设失败。
所以我们假设T2不是最优树,T2'是。最理想我们应该推出T1不是最优树,那么T2不是最优树这个假设就不成立。完结。
开始,假设T2不是最优树,T2'是,根据定理,T2'<T2 等同于 T1'+X+Y<T1+X+Y 等同于 T1'<T1
也就是说T1不是一个最小树和前提有矛盾。假设不成立。完结
赫夫蔓树的扩展:
找了个证明.没看.先放着.不过大概想下,有贪心算法的思想。权重之和(wpl)最小,那么就选2个最小的,组合,并相加,放入剩下的。再比较。又还有动态算法的思想。
定理1:哈夫曼树是最优的。
证明:用归纳法证明。
- 基本情况: 当 n=2 时, 哈夫曼树具有最小权重外部路径(EPW),因为树 仅有二种可能,有二个叶结点的二种哈夫曼树下的EPW是相同的。
- 假设: 设有哈夫曼树有 n−1 个叶子时,定理成立。
- 推导: 令T为有n (n>=2)个叶子的哈夫曼树。不失 一般性,设 w1 <= w2 <=... <=wn。令V 是w1 与w2的父结点。由引理1知, 在T中,不存在叶结点,其深度大于叶结点w1 与w2的深度。若存在深度大于w1, w2深度的结点,我们可以通过将之与w1, w2交换,由此得到更小的WPL。按如下方式得到到二叉树T':以结点V'替换结点V, 其中V'的权重是w1+w2,则T'是相应于{w1+w2,w3,...,wn}的一棵哈夫曼树。根据归纳假设,T'具有最小权重外部路径,T是最优的(EPW最小)。在T'的结点V'上添加叶结点w1, w2,可得T,则T是具有最小权重外部路径的哈夫曼树。由此,我们由数学照片纳法证明了定理1.
//自设场景。 //假设一个游戏的按键,w,a,s,d,j,k,l 的预估使用频率为8,7,5,8,40,17,15. //设计一个二进制编码,使得编码最短,并计算比等长,压缩率大概高多少? public static void bitCode() { operateChar wChar=new operateChar('w', 8); operateChar aChar=new operateChar('a', 7); operateChar sChar=new operateChar('s', 5); operateChar dChar=new operateChar('d', 8); operateChar jChar=new operateChar('j', 40); operateChar kChar=new operateChar('k', 17); operateChar lChar=new operateChar('l', 15); List<MyBinaryNode<operateChar>> weightList=new ArrayList<MyBinaryNode<operateChar>>(); weightList.add(new MyBinaryNode<Test.operateChar>(wChar, null, null)); weightList.add(new MyBinaryNode<Test.operateChar>(aChar, null, null)); weightList.add(new MyBinaryNode<Test.operateChar>(sChar, null, null)); weightList.add(new MyBinaryNode<Test.operateChar>(dChar, null, null)); weightList.add(new MyBinaryNode<Test.operateChar>(jChar, null, null)); weightList.add(new MyBinaryNode<Test.operateChar>(kChar, null, null)); weightList.add(new MyBinaryNode<Test.operateChar>(lChar, null, null)); mycompare theCompare=new mycompare(); weightList.sort(theCompare); //create hafuman tree. MyBinaryTree<operateChar> theTree=new MyBinaryTree<operateChar>(null); MyBinaryNode<operateChar> theRoot=theTree.rootNode; while(weightList.size()>=2) { MyBinaryNode<operateChar> min1=weightList.get(0); MyBinaryNode<operateChar> min2=weightList.get(1); operateChar tempChar=new operateChar('-', min1.element.weight+min2.element.weight); MyBinaryNode<operateChar> tempRoot=new MyBinaryNode<Test.operateChar>(tempChar, min1, min2); weightList.remove(0); weightList.remove(0); weightList.add(0, tempRoot); weightList.sort(theCompare); theTree.rootNode=tempRoot; } //set code. setCode(theTree.rootNode, ""); for(int i=0;i<tempret.size();i++) { //System.out.println(tempret.get(i).element.vv+":"+tempret.get(i).element.weight+". code:"+tempret.get(i).element.binaryCode+"\r\n"); } int normalSize=0; int hefumanSize=0; for(int i=0;i<tempret.size();i++) { hefumanSize+=tempret.get(i).element.binaryCode.length()*tempret.get(i).element.weight; normalSize+=3*tempret.get(i).element.weight; } double rate=(double)hefumanSize/(double)normalSize; System.out.println("hefuman 压缩为原来的:"+rate);//等长的话,7个符号,最小长度是3位。 //theTree.printTree(0); } private static List<MyBinaryNode<operateChar>> tempret=new ArrayList<MyBinaryTree.MyBinaryNode<operateChar>>(); private static void setCode(MyBinaryNode<operateChar> rootnode,String code) { if(rootnode!=null) { rootnode.element.binaryCode=code; if(rootnode.leftNode==null && rootnode.rightNode==null) { tempret.add(rootnode); } } if(rootnode.leftNode!=null) { setCode(rootnode.leftNode, code+"0"); } if(rootnode.rightNode!=null) { setCode(rootnode.rightNode, code+"1"); } }