Распечатка правильного пути в дереве Хаффмана

1

Предполагается, что этот метод принимает FileInputStream и символ по символу, он должен возвращать StringBuilder всех 0 и 1, которые приводят к этому символу в этом дереве Хаффмана. Однако у меня возникают некоторые проблемы, когда он просто возвращает экземпляр каждого пути.

Например, если у меня есть дерево:

                             (10)

            (4)                               (6)

  (2=' ')             (2)                 (3='a') (3='b')

                (1=EOF)  (1='c')

Файл:

ab ab cab

Будет возвращено намного больше 1 и 0, чем ожидалось. Я тестировал свои методы построения дерева, и они, похоже, работают. Однако, я предполагаю, что что-то не так с моим рекурсивным методом, compress(). Я считаю, что это потому, что, когда он достигает листа, который не содержит желаемого символа, он все равно возвращает строку пути к этому листу. Из-за этого он вернется намного больше, чем ожидалось. Если это правда, то как я могу удалить путь к этому листу, если он не совпадает?

Редактирование: Я работаю через все выходные и здесь, что у меня есть: Клиентский код, который содержит графический интерфейс, действительно длинный, поэтому я его не заметил. Я также опустил методы для печати дерева, поскольку здесь уже достаточно кода.

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

    public class HuffmanTree {
        public HuffmanNode overallRoot;
        Map<Character, Integer> storage; // gets the repeating times of a number
        ArrayList<HuffmanNode> nodes; // stores all nodes (will have only one node later)

        // constructor
        public HuffmanTree(Map<Character, Integer> counts) {
            storage = counts; // sets the map to this one // putAll instead?
            storage.put((char)4, 1); // put end of file character
            storage = sortByValue(storage); // map is now sorted by values
            nodes = storeNodes();
            createTree();
        }

        // creates nodes from each key/value in storage map
        private ArrayList<HuffmanNode> storeNodes() {
            List<Character> characters = new ArrayList<Character>(storage.keySet());
            ArrayList<HuffmanNode> output = new ArrayList<HuffmanNode>(); // stores all the nodes
            for (Character i: characters) {
                HuffmanNode temp = new HuffmanNode(storage.get(i), i);
                output.add(temp);
            }   
            return output; // output will be sorted by occurrences
        }

        // post: helper that sorts the map by value code 
        // Source: http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java
        private static <Character, Integer extends Comparable<? super Integer>> Map<Character, Integer> 
            sortByValue( Map<Character, Integer> map ) {

            List<Map.Entry<Character, Integer>> list =
            new LinkedList<Map.Entry<Character, Integer>>( map.entrySet() );
            Collections.sort( list, new Comparator<Map.Entry<Character, Integer>>() {
                public int compare( Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2 ) {
                return (o1.getValue()).compareTo( o2.getValue() );
                }

            } );

            Map<Character, Integer> result = new LinkedHashMap<Character, Integer>();
            for (Map.Entry<Character, Integer> entry : list) {
                result.put( entry.getKey(), entry.getValue() );
            }
            return result;
        }

        // takes stuff from nodes and creates relationships with them
        private void createTree() {
            do { // keep running until nodes has only one elem
                HuffmanNode first = nodes.get(0); // gets the first two nodes
                HuffmanNode second = nodes.get(1);
                HuffmanNode combined;

                combined = new HuffmanNode(first.frequency + second.frequency); // combined huffman node
                combined.left = first;
                combined.right = second;

                nodes.remove(0); // then remove the first two elems from list
                nodes.remove(0);

                // goes through and adds combined into right spot
                boolean addAtEnd = true;
                for (int i = 0; i < nodes.size(); i++) {
                    if (nodes.get(i).frequency > combined.frequency) {
                        nodes.add(i, combined);
                        addAtEnd = false; // already added; don't add at end
                        break;
                    }
                } // need to add at end 
                if (addAtEnd) {
                    nodes.add(combined);
                }
                if (nodes.size() == 1) {
                    break;
                }

            } while (nodes.size() > 1);
        }

        // inputFile is a textFile // puts contents of file onto display window
        // nodes need to be made first
        // This is responsible for outputting 0 and 1's
        public StringBuilder compress(InputStream inputFile) throws IOException {
            StringBuilder result = new StringBuilder(); // stores resulting 1 and 0 
            byte[] fileContent = new byte[20000000]; // creates a byte[]
            inputFile.read(fileContent);                // reads the input into fileContent
            String storage = new String(fileContent);  // contains entire file into this string to process

            // need to exclude default value
            String storage2 = ""; // keeps chars of value without default values
            for (int i = 0; i < storage.length(); i++) {
                if (storage.charAt(i) != '\u0000') {
                    storage2+=storage.charAt(i);
                } else {
                    break;
                }
            }

            for (int i = 0; i < storage2.length(); i++) { // goes through every char to get path
                String binary = findPath(storage2.charAt(i));
                result.append(binary); // add path to StringBuilder
            }
            return result;  
        }

        // return a stringbuilder of binary sequence by reading each character, searching the
        // tree then returning the path of 0 and 1's
        private String findPath(char input) {
            return findPath(input, nodes.get(0), "");
        }

        private String findPath(char input, HuffmanNode root, String path) {
            String result = "";
            if (!root.isLeaf()) {
                result += findPath(input, root.left, path += "0"); // go left
                result += findPath(input, root.right, path += "1"); // go right
            } if (root.isLeaf()) { // base case If at right leaf
                if (input == root.character) {
                    //System.out.println("found it");
                    return path;
                }
            }   
            return result;
        }
    }

Ниже представлен отдельный класс узлов:

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

// Stores each character, its number of occurrences, and connects to other nodes
public class HuffmanNode implements Comparable<HuffmanNode>{
    public int frequency;
    public char character;
    public HuffmanNode left;
    public HuffmanNode right;


    // constructor for leaf
    public HuffmanNode(int frequency, char character) {
        this.frequency = frequency;
        this.character = character;
        left = null;
        right = null;
    }

    // constructor for node w/ children
    public HuffmanNode(int frequency) {
        this.frequency = frequency;
        left = null;
        right = null;
    }

    // provides a count of characters in an input file and place in map
    public static Map<Character, Integer> getCounts(FileInputStream input) throws IOException {
        Map<Character, Integer> output = new TreeMap<Character, Integer>(); // treemap keeps keys in sorted order (chars alphabetized)
        byte[] fileContent = new byte[2000000]; // creates a byte[]
        //ArrayList<Byte> test = new ArrayList<Byte>();
        input.read(fileContent);                // reads the input into fileContent
        String test = new String(fileContent);  // contains entire file into this string to process
        //System.out.println(test);

        // goes through each character of String to put chars as keys and occurrences as keys
        int i = 0;
        char temp = test.charAt(i);
        while (temp != '\u0000') { // while does not equal default value
            if (output.containsKey(temp)) { // seen this character before; increase count

                int count = output.get(temp);
                output.put(temp, count + 1);
                //System.out.println("repeat; char is: " + temp + "count is: " + output.get(temp)); // test
            } else {                        // Haven't seen this character before; create count of 1    
                output.put(temp, 1);
                //System.out.println("new; char is: " + temp + "count is: 1"); // test
            }
            i++;
            temp = test.charAt(i);
        }
        return output;
    }

    // sees if this node is a leaf
    public boolean isLeaf() {
        if (left == null && right == null) {
            return true;
        }
        return false;
    }

    @Override
    public int compareTo(HuffmanNode o) {
        // TODO Auto-generated method stub
        return 0;
    }
}
Теги:
recursion
binary-tree
huffman-code

2 ответа

1
Лучший ответ

Проблема private String findPath(char input, HuffmanNode root, String path). Кажется, вы пытаетесь использовать backtrack для поиска дерева, но вы никогда не отказываетесь от чего-либо при возвращении.

Простым решением является возврат значения null для листа, который не является правильным символом, так что сохраняйте только правильный путь:

private String findPath(char input, HuffmanNode root, String path) {
    String result;
    if (! root.isLeaf()) {
        if ((result = findPath(input, root.left, path + '0')) == null) {
            result = findPath(input, root.right, path + '1');
        }
    }
    else {
        result = (input == root.character) ? path : null;
    }
    return result;
}

Протестировано с помощью вашего примера, правильно дает findPath('a', root, "") = "10" и findPath('c', root, "") = "011"


Но вы выполняете последовательный поиск каждого символа, читаемого с ввода. IMHO, более эффективно сначала создать хеш с каждым символом в качестве ключа, а путь - как значение:

private Map<Character, String> genMap(HuffmanNode root) {
    Map<Character, String> map = new HashMap<Character, String>();
    huffmanTreeAdd(map, root, "");
    return map;
}

private void huffmanTreeAdd(Map<Character, String> map, HuffmanNode root, String path) {
    if (root.isLeaf()) {
        map.put(root.character, path);
    }
    else {
        huffmanTreeAdd(map, root.left, path + '0');
        huffmanTreeAdd(map, root.right, path + '1');
    }
}

Таким образом вы получаете прямой путь для каждого символа, читаемого на входе, с помощью String path = map.get(c); с хэш-поиском вместо последовательного.

0

Я предлагаю вам использовать полиморфизм для двух разных типов узлов, которые у вас есть. Что-то вроде шаблона проектирования интерпретатора. Это поможет вашему коду как в удобочитаемости, так и в эффективности:

abstract class HuffmanNode {
  private String code;
  public abstract int getFrequency();
  public abstract String getCode();

  public abstract void generateCodes(String code, Map<Character, String> codes);
  ...
}

class InternalNode {
  private HuffmanNode left;
  private HuffmanNode right;
  ...
  public void generateCodes(String code, Map<Character, String> codes) {
    left.generateCodes(code + "0", codes);
    right.generateCodes(code + "1", codes);
  }
  ...
}

class CharacterNode {
  private int frequency;
  private char character;
  ...
  public void generateCodes(String code, Map<Character, String> codes) {
    codes.put(character, code);
  }
  ...
}

Вы можете заполнить codes для всего дерева, как это (запустить только один раз):

Map<Character, String> codes = new HashMap<>();
root.generateCodes("", codes);

а затем используйте codes.get(input) вместо findPath. У вас могут быть очень чистые рекурсивные реализации для других методов, таких как getFrequency().

Ещё вопросы

Сообщество Overcoder
Наверх
Меню