【问题标题】:Huffman Tree in JavaJava中的霍夫曼树
【发布时间】:2013-05-20 21:06:15
【问题描述】:

我的霍夫曼树代码有问题。在主要方法中,我输入了一个符号字符串,还输入了一个包含符号频率的整数数组。它应该打印出每个符号及其霍夫曼代码,但我认为它是错误的......

代码如下:

 package huffman;

import java.util.*;

abstract class HuffmanTree implements Comparable<HuffmanTree> {
    public final int frequency; // the frequency of this tree
    public HuffmanTree(int freq) { frequency = freq; }

    // compares on the frequency
    public int compareTo(HuffmanTree tree) {
        return frequency - tree.frequency;
    }
}

class HuffmanLeaf extends HuffmanTree {
    public final char value; // the character this leaf represents

    public HuffmanLeaf(int freq, char val) {
        super(freq);
        value = val;
    }
}

class HuffmanNode extends HuffmanTree {
    public final HuffmanTree left, right; // subtrees

    public HuffmanNode(HuffmanTree l, HuffmanTree r) {
        super(l.frequency + r.frequency);
        left = l;
        right = r;
    }
}

public class Huffman {
    // input is an array of frequencies, indexed by character code
    public static HuffmanTree buildTree(int[] charFreqs, char[] test2) {
        PriorityQueue<HuffmanTree> trees = new PriorityQueue<HuffmanTree>();
        // initially, we have a forest of leaves
        // one for each non-empty character
        for (int i = 0; i < charFreqs.length; i++)
            if (charFreqs[i] > 0)
                trees.offer(new HuffmanLeaf(charFreqs[i], test2[i]));

        assert trees.size() > 0;
        // loop until there is only one tree left
        while (trees.size() > 1) {
            // two trees with least frequency
            HuffmanTree a = trees.poll();
            HuffmanTree b = trees.poll();

            // put into new node and re-insert into queue
            trees.offer(new HuffmanNode(a, b));
        }
        return trees.poll();
    }

    public static void printCodes(HuffmanTree tree, StringBuffer prefix) {
        assert tree != null;
        if (tree instanceof HuffmanLeaf) {
            HuffmanLeaf leaf = (HuffmanLeaf)tree;

            // print out character, frequency, and code for this leaf (which is just the prefix)
            System.out.println(leaf.value + "\t" + leaf.frequency + "\t" + prefix);

        } else if (tree instanceof HuffmanNode) {
            HuffmanNode node = (HuffmanNode)tree;

            // traverse left
            prefix.append('0');
            printCodes(node.left, prefix);
            prefix.deleteCharAt(prefix.length()-1);

            // traverse right
            prefix.append('1');
            printCodes(node.right, prefix);
            prefix.deleteCharAt(prefix.length()-1);
        }
    }

    public static void main(String[] args) {
        //Symbols:
        String str = "12345678"; 
        char[] test2 = str.toCharArray();
        //Frequency (of the symbols above):
        int[] charFreqs = {36,18,12,9,7,6,5,4};


        // build tree
        HuffmanTree tree = buildTree(charFreqs,test2);

        // print out results
        System.out.println("SYMBOL\tFREQ\tHUFFMAN CODE");
        printCodes(tree, new StringBuffer());
    }
}

我得到的输出是:

SYMBOL  FREQ    HUFFMAN CODE
1           36          0
3           12          100
6           6           1010
5           7           1011
2           18          110
4           9           1110
8           4           11110
7           5           11111

这很奇怪,例如符号 7 应该是:11110 而符号 8 应该是:11111

你能帮帮我吗?

【问题讨论】:

  • 陈述理由:“符号 7 应为:11110,符号 8 应为:11111” - 为什么必须这样?
  • @user2246674 我在纸上画了这个霍夫曼树的例子,结果不匹配。我是新手,所以也许我在某个地方犯了一个简单的错误。

标签: java huffman-code


【解决方案1】:

位模式的分配与代码的最优性无关。你的任务会很好。没有什么奇怪的。您也可以对 2:110、3:100 或 4:1110、5:1011 表示担忧,但也可以。

对代码强加顺序的唯一原因是减少将代码从压缩器传送到解压缩器所需的位数。您可以发送每个符号的代码长度,而不是发送代码,只要代码的两侧构造相同,仅从长度即可。

在这种情况下,方法通常是按数字顺序将代码分配给已排序的符号列表。那么你确实会得到符号 7 的代码“值”低于符号 8,如果这是它们被分配的顺序的话。

对于您的示例,这样的规范代码将是:

1: 1 - 0
2: 3 - 100
3: 3 - 101
4: 4 - 1100
5: 4 - 1101
6: 4 - 1110
7: 5 - 11110
8: 5 - 11111

您只需获取长度并在相同长度内对符号进行排序。然后分配从 0 开始并递增的代码,随着长度的增加在末尾添加位。

请注意,这是一个不寻常的示例,其中符号顺序也是频率顺序。通常情况并非如此。

【讨论】:

  • 啊我明白了,那么在这种情况下如何按数字顺序分配代码?
  • 嗨,马克,感谢您的帮助,但我真的不明白您是如何获得这些代码的?我需要对代码进行大量更改吗?
  • 我没有时间阅读和调试您的代码。我通过简单地数数得到了这些代码。在每个位长度内,只是递增。 1101 之后是 1110。当逐步提高到更大的位长度时,递增然后添加足够的零以达到该位长度。 101 后面是 1100。第一个代码全为零。如果代码正确且完整,则最后一个代码将全为 1。要使具有给定位长度的特定符号集的代码唯一,请按每个位长度内的符号顺序排序。
【解决方案2】:

只需再添加一个 0 即可理解完成位。 (超过 3 位读数)

1 36 0 3 12 100 6 6 1010 5 7 1011'0 2 18 110 4 9 1110 8 4 11110 7 5 11111'0

【讨论】:

    【解决方案3】:

    从答案的cmets中回答问题:

    嘿,马克,感谢您的帮助,但我真的不明白您是如何获得这些代码的?我需要对代码进行大量更改吗?

    Mark 只是指霍夫曼编码的目标,即为每个符号找到最有效的深度(位数),以便所有符号的整体编码(所有符号的频率 [符号] * 代码长度 [符号])为最小化。

    所以实际上你需要做的就是确定每个符号的叶子的深度(级别)。现在按每个符号的深度对其进行排序并开始计算它们。

    现在您只需要向上计数即可。就这么简单:

    Example:
    2x2: 00, 01  (next is 10)
    4x3: 10 + (00, 01, 10) = 1000, 1001, 1010 (next is 1011)
    5x3: 1011 + (0, 1, 0 + 10) = 10110, 10111, 10110 + 10 = 11000 (next would be 11001)...
    

    最后一部分显示了如果元素的数量大于两组之间的可用位差会发生什么。它只是被添加到前缀中。

    这样创建的霍夫曼代码使用最少的空间。由于这只是一棵树,您也可以从 11111 开始并删除 1 并获得另一个在位数方面同样有效的代码系统。

    可以添加的一件事是,有一个修改可以将比较中的 1(或 0)的数量增加到 0(或 1),这样您就有机会进一步压缩那些用于进一步压缩消息的位模式。


    总结:按符号在频率树中的深度对符号进行排序。通过加(减)一来构造代码。下一个免费代码是下一组的开始前缀。对于每个新成员,只需添加(减去)。然后您从在代码中填充 0 (1) 开始。

    为了存储这样的树,请记住,对相同的符号组使用相同的算法,您只需要存储以下信息:

    组数 n, n * { +bitDepth, 符号数 i, s1,s2...si}。由于 n、+bitDepth、符号数量本身的存储是压缩的主题,您可以使用可变位格式,甚至可以发出霍夫曼树,因为您会发现统计不均匀分布,这是您可以看到压缩发生的主要原因用霍夫曼树。

    【讨论】:

    • 我有点困惑,说我有 3 个深度为 2 的节点。我以为第一个节点是 00,然后是 01,然后是 10。但是实际的树是 01、10、11 .为什么第一个不是从00开始的?
    【解决方案4】:

    发布我基于 Princeton.EDU 版本创建的 Java 中霍夫曼树的完整实现。普林斯顿包的版本可以作为学术示例,但在上下文之外并不能真正使用。

    下面的示例可以通过提供一个字符串作为输入并获取一个字节数组作为输出来使用。

    如果您想将输出编码为人类可读的格式,请使用标准 java Base64 编码器(空间效率非常低)或查看我发布的 HumanByte 类 here

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.util.PriorityQueue;
    
    /**************************************************************************************************************
     * Used edu.princeton.cs version of huffman algorithm and made it easily usable outside of example context
     * replaced proprietary priority queue by Java standard one
     * created binary output/input that has minimal features needed
     *
     * If human readable encoding of huffman encoded string is desired then look at HumanByte class
     * that was posted here
     * 
     * https://stackoverflow.com/questions/4141317/how-to-convert-byte-array-into-human-readable-format/58332208#58332208
     *
     * @author Stan Sokolov
     * 10/9/19
     **************************************************************************************************************/
    public class BetterHuffman {
        private static final int R = 256;
        private static final char PARENT = '\u0000';
        private static final char EMPTY = '\u0001';
        private static final int UNDEFINED = -1;
    
    
        /**************************************************************************************************************
         *   Compress string in bytes
         **************************************************************************************************************/
        public static byte[] compress(final String s) {
    
            final HuffmanOut binaryOut = new HuffmanOut();
            final char[] input = s.toCharArray();
            final int[] freq = new int[R];
    
            for (char anInput1 : input) {
                ++freq[anInput1];
            }
    
            final BetterHuffman.Node root = buildTrie(freq);
            final String[] st = new String[R];
            buildCode(st, root, "");
    
            writeTrie(root, binaryOut);
            binaryOut.write(input.length);
    
    
            for (char anInput : input) {
                final String code = st[anInput];
                for (char ch : code.toCharArray()) {
                    binaryOut.writeBit(ch == '1');
                }
            }
    
            return binaryOut.value();
        }
    
        /**************************************************************************************************************
         *   build huffman tree
         **************************************************************************************************************/
        private static BetterHuffman.Node buildTrie(int[] freq) {
            final PriorityQueue<BetterHuffman.Node> pq2 = new PriorityQueue<>();
    
            for (char i = 0; i < R; ++i) {
                if (freq[i] > 0) {
                    //pq.insert(new BetterHuffman.Node(i, freq[i], null, null));
                    pq2.add(new BetterHuffman.Node(i, freq[i], null, null));
                }
            }
    
            if (pq2.size() == 1) {//if entire string is just one char repeated
                if (freq[0] == 0) {//empty string
                    pq2.add(new BetterHuffman.Node(PARENT, 0, null, null));
                } else {
                    pq2.add(new BetterHuffman.Node(EMPTY, 0, null, null));
                }
            } else
                while (pq2.size() > 1) {
                    final BetterHuffman.Node left = pq2.poll();
                    final BetterHuffman.Node right = pq2.poll();
                    //aggregate two nodes into one by summing frequency
                    final BetterHuffman.Node parent = new BetterHuffman.Node(PARENT, left.freq + right.freq, left, right);
                    pq2.add(parent);
                }
    
            //this will be the root node that would have total length of input as frequency
            return pq2.poll();
        }
    
        /**************************************************************************************************************
         *   write tree into byte output
         **************************************************************************************************************/
        private static void writeTrie(final BetterHuffman.Node x, final HuffmanOut binaryOut) {
            if (x.isLeaf()) {//if this is a node representing symbol in alphabet
                binaryOut.writeBit(true);
                binaryOut.writeByte((int) x.ch);
            } else {
                binaryOut.writeBit(false); //this is an aggregate node used for branching
                writeTrie(x.left, binaryOut);
                writeTrie(x.right, binaryOut);
            }
        }
    
        /**************************************************************************************************************
         *   make substitutes for incoming words
         **************************************************************************************************************/
        private static void buildCode(final String[] st, final BetterHuffman.Node x, final String s) {
            if (!x.isLeaf()) {
                buildCode(st, x.left, s + '0');
                buildCode(st, x.right, s + '1');
            } else {
                st[x.ch] = s;
            }
        }
    
        /**************************************************************************************************************
         *   Return uncompressed string
         **************************************************************************************************************/
        public static String expand(final byte[] value) {
    
            final HuffmanIn binaryIn = new HuffmanIn(value);
            final StringBuilder out = new StringBuilder();
    
            final BetterHuffman.Node root = readTrie(binaryIn);
            final int length = binaryIn.readInt();
    
            for (int i = 0; i < length; ++i) {
                BetterHuffman.Node x = root;
    
                while (!x.isLeaf()) {
                    boolean bit = binaryIn.readBoolean();
                    if (bit) {
                        x = x.right;
                    } else {
                        x = x.left;
                    }
                }
    
                out.append(x.ch);
            }
    
    
            return out.toString();
        }
    
        /**************************************************************************************************************
         *   get tree from bytes
         **************************************************************************************************************/
        private static BetterHuffman.Node readTrie(final HuffmanIn binaryIn) {
            boolean isLeaf = binaryIn.readBoolean();
            if (isLeaf) {
                char ch = binaryIn.readChar();
                return new BetterHuffman.Node(ch, UNDEFINED, null, null);
            } else {
                return new BetterHuffman.Node(PARENT, UNDEFINED, readTrie(binaryIn), readTrie(binaryIn));
            }
        }
    
    
        /**************************************************************************************************************
         *   Simple implementation of node
         **************************************************************************************************************/
        private static class Node implements Comparable<Node> {
            private final char ch;
            private final int freq;
            private final Node left;
            private final Node right;
    
            Node(char ch, int freq, Node left, Node right) {
                this.ch = ch;
                this.freq = freq;
                this.left = left;
                this.right = right;
            }
    
            private boolean isLeaf() {
                return left == null && right == null;
            }
    
            @Override
            public int compareTo(Node that) {
                return this.freq - that.freq;
            }
        }
    
        /**************************************************************************************************************
         *   class to read bits from stream
         **************************************************************************************************************/
        private static class HuffmanIn {
    
            private final ByteArrayInputStream in;
            private int buffer;
            private byte n;
    
            HuffmanIn(final byte[] input) {
                in = new ByteArrayInputStream(input);
                fillBuffer();
            }
    
            private void fillBuffer() {
                buffer = in.read();
                n = 8;
            }
    
            boolean readBoolean() {
                boolean bit = (buffer >> --n & 1) == 1;
                if (n == 0) {
                    fillBuffer();
                }
                return bit;
            }
    
            char readChar() {
                int x = buffer <<= 8 - n;
                if (n == 8) {
                    fillBuffer();
                } else {
                    byte oldN = n;
                    fillBuffer();
                    n = oldN;
                    x |= buffer >>> n;
                }
                return (char) (x & 255);
            }
    
            int readInt() {
                int x = 0;
                for (int i = 0; i < 4; ++i) {
                    char c = readChar();
                    x <<= 8;
                    x |= c;
                }
                return x;
            }
        }
    
        /**************************************************************************************************************
         *   Output
         **************************************************************************************************************/
        private static class HuffmanOut {
    
            private ByteArrayOutputStream out = new ByteArrayOutputStream();
            private int buffer;
            private byte n;
    
            /**************************************************************************************************************
             * @return what was compressed so far in a human readable (no funny characters) format
             **************************************************************************************************************/
            public byte[] value() {
                clearBuffer();
                return out.toByteArray();
            }
    
            void writeBit(final boolean bit) {
                buffer = (buffer <<= 1) | (bit ? 1 : 0);
                if (++n == 8) {
                    clearBuffer();
                }
            }
    
            void writeByte(final int x) {
                for (int i = 0; i < 8; ++i) {
                    writeBit((x >>> 8 - i - 1 & 1) == 1);
                }
            }
    
            void clearBuffer() {
                if (n != 0) {
                    out.write(buffer <<= 8 - n);
                    n = 0;
                    buffer = 0;
                }
            }
    
    
            /**************************************************************************************************************
             *   write all 4 bytes of int
             **************************************************************************************************************/
            void write(final int x) {
                for (int i = 3; i >= 0; i--)
                    writeByte(x >>> (i * 8) & 255);//write 4 bytes of int
            }
    
    
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多