Компаратор и приоритетные очереди

#java #hashmap #priority-queue #comparator #huffman-code

#java #hashmap #приоритетная очередь #компаратор #хаффман-код

Вопрос:

Я нахожусь в процессе кодирования кода Хаффмана, где я импортирую файл, генерирую код Хаффмана для каждого символа, затем вывожу двоичный файл в файл. Для импорта символов я использую сканер, который считывает каждый символ, помещает его в узел, который имеет значения считанного символа и частоту 1. Затем узел добавляется в PriorityQueue. Поскольку класс Node имеет метод compareTo, который сравнивает только частоту, как я могу реализовать компаратор для этого конкретного PriorityQueue, который сравнивает символы при сортировке в очереди?

Буквальный пример: Очередь символов должна быть отсортирована следующим образом:

 [A:1][A:1][A:1][B:1][C:1]
Next step:
[A:1][A:2][B:1][C:1]
Final:
[A:3][B:1][C:1]
  

Вот несколько фрагментов:

 protected class Node implements Comparable<Node>{
    Character symbol;
    int frequency;
    
    Node left = null;
    Node right = null;
    @Override
    public int compareTo(Node n) {
        return n.frequency < this.frequency ? 1 : (n.frequency == this.frequency ? 0 : -1);
    }
    
    public Node(Character c, int f){
        this.symbol = c;
        this.frequency = f;
    }
    public String toString(){
        return "[" this.symbol  "," this.frequency "]";
    }
  

Это PriorityQueue, для которого требуется пользовательский компаратор:

 public static PriorityQueue<Node> gatherFrequency(String file) throws Exception{
    File f = new File(file);
    Scanner reader = new Scanner(f);
    PriorityQueue<Node> PQ = new PriorityQueue<Node>();
    while(reader.hasNext()){
        for(int i = 0; i < reader.next().length();i  ){
            PQ.add(new Node(reader.next().charAt(0),1));
        }
    }
    if(PQ.size()>1){ //during this loop the nodes should be compared by character value
        while(PQ.size() > 1){
            Node a = PQ.remove();
            Node b = PQ.remove();
            if(a.symbol.compareTo(b.symbol)==0){
                Node c = new Node(a.symbol, a.frequency   b.frequency);
                PQ.add(c);
            }
            else break;
        }
        return PQ;
    }
    return PQ;
    
}
  

Это новый метод, который я создал, используя HashMap:

 public static Collection<Entry<Character,Integer>> gatherFrequency(String file) throws Exception{
        File f = new File(file);
        Scanner reader = new Scanner(f);
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        while(reader.hasNext()){
            for(int i = 0; i < reader.next().length();i  ){
                Character key = reader.next().charAt(i);
                if(map.containsKey(reader.next().charAt(i))){
                    int freq = map.get(key);
                    map.put(key, freq 1);
                }
                else{
                    map.put(key, 1);
                }
            }
        }
        return map.entrySet();
    }

    
  

Комментарии:

1. Это, по-видимому, намного сложнее, чем должно быть. Не следует ли подсчитывать все A , даже если они не являются последовательными.

2. Они всегда будут последовательными, если они находятся в PriorityQueue, который сортирует по значению символа

Ответ №1:

Стандартный подход к реализации деревьев Хаффмана заключается в использовании хэш-карты (в Java вы, вероятно, использовали бы HashMap<Character, Integer> ) для подсчета частоты для каждой буквы и вставки в очередь приоритетов одного узла для каждой буквы. Итак, при построении самого дерева Хаффмана вы начинаете с приоритетной очереди, которая уже находится в «конечном» состоянии, которое вы показали. Затем алгоритм Хаффмана повторно извлекает два узла из очереди приоритетов, создает новый родительский узел для этих двух узлов и вставляет новый узел в очередь приоритетов.

Комментарии:

1.@TrevorMA: Если вам действительно не нужно небольшое улучшение производительности, предлагаемое TIntIntHashMap , я рекомендую вам использовать стандарт HashMap , в частности, поскольку вы им раньше не пользовались (это будет хорошая возможность изучить его, и вы будете сталкиваться с ним HashMap гораздо чаще, чем TIntIntHashMap ).

2. @TrevorMA: Верно; это может немного сбить с толку. put() Метод используется как для первого размещения чего-либо в hashmap, так и для замены существующего значения. Допустим, у вас есть символ, сохраненный в переменной c ; тогда вам сначала нужно проверить, содержит ли hashmap c в качестве ключа. Если это произойдет, вы можете прочитать текущую частоту с помощью get() , вычислить частоту 1 и обновить частоту в хэш-карте с помощью put() . Если ключа там нет, вы можете добавить его с частотой 1.

3. @TrevorMA: Нет проблем. Используйте entrySet() метод, который предоставляет вам коллекцию записей карты, где каждая запись содержит как ключ, так и значение.

4. @TrevorMA: Рад, что это помогло. Кстати, не забудьте каким-то образом включить описание дерева Хаффмана в выходной файл, поскольку последовательность битов, создаваемая алгоритмом, не очень полезна, если у вас нет дерева … 😉

5. @TrevorMA: После выполнения Character key = reader.next().charAt(i); вы вызываете reader.next() еще раз, таким образом считывая еще один символ. Этот символ, вероятно, будет отличаться от того, который сейчас хранится в key , и вы спрашиваете, присутствует ли в словаре второй символ — и если это так, вы пытаетесь обновить key . (Редактировать: теперь я вижу, что вы вызываете next() много раз — каждый вызов next выдает вам следующую строку из входных данных… Используйте next() только один раз во всем while теле цикла.)