Простая реализация Trie

Мне нужно реализовать Trie (на Java) для проекта колледжа. Trie должен иметь возможность добавлять и удалять строки (для фазы 1).

Я тратил несколько часов каждый день (в течение последних нескольких дней), пытаясь понять, как это сделать, и каждый раз терпел неудачу.

Мне нужна помощь, примеры в Интернете и мой учебник (Структуры данных и алгоритмы в Java, Адам Дроздек) не помогают.

Информация

  1. Классы узлов, с которыми я работаю:

    class Node {
        public boolean isLeaf;
    }
    
    class internalNode extends Node {
        public String letters;  //letter[0] = '$' always.
        //See image -> if letter[1] = 'A' then children[1] refers to child node "AMMO"
        //See image -> if letter[2] = 'B' then children[2] refers to internal node "#EU"
        public TrieNode[] children = new TrieNode[2];
    
        public TrieInternalNode(char ch)
        {
            letters = "#" + String.valueOf(ch);//letter[0] = '$' always.
            isLeaf = false;
        }
    }
    
    class leafNode extends Node
    {
        public String word;
        public TrieLeafNode(String word)
        {
            this.word = new String(word);
            isLeaf = true;
        }
    }
    
  2. И вот псевдокод для вставки, которому мне нужно следовать: (предупреждение, это очень расплывчато)

    trieInsert(String K)
    {
        i = 0;
        p = the root;
        while (not inserted)
        {
            if the end of word k is reached
                set the end-of-word marker in p to true;
            else if (p.ptrs[K[i]] == 0)
                create a leaf containing K and put its address in p.ptrs[K[i]];
            else if reference p.ptrs[K[i]] refers to a leaf
            {
                K_L = key in leaf p.ptrs[K[i]]
                do
                {
                    create a nonleaf and put its address in p.ptrs[K[i]];
                    p = the new nonleaf;
                } while (K[i] == K_L[i++]);
            }
            create a leaf containing K and put its address in p.ptrs[K[--i]];
            if the end of word k is reached
                set the end-of-word marker in p to true;
            else
                create a leaf containing K_L and put its address in p.ptrs[K_L[i]];
            else
                p = p.ptrs[K[i++]];
        }
    }
    
  3. Мне нужно реализовать следующие методы.

    public boolean add(String word){...}//adds word to trie structure should return true if successful and false otherwise
    
    public boolean remove(String word){...}//removes word from trie structure should return true if successful and false otherwise
    
  4. Я не могу найти псевдокод для удаления, но если вставка не работает, удаление мне не поможет.

  5. Вот изображение того, как должен выглядеть Trie, который мне нужно реализовать.

введите здесь описание изображения

  1. Я знаю, что Trie по-прежнему будет неэффективным, если будет реализован таким образом, но на данный момент мне не нужно беспокоиться об этом.

  2. В книге представлена ​​реализация, похожая на то, что мне нужно сделать, но не использует конец слова char ('$') и сохраняет только слова без их префиксов в дочерних узлах http://mathcs.duq.edu/drozdek/DSinJava/SpellCheck.java

Ограничения

  1. Мне нужно реализовать три в JAVA.
  2. Я не могу импортировать или использовать какие-либо встроенные структуры данных Java. (т.е. нет Map, HashMap, ArrayList и т. д.)
  3. Я могу использовать массивы, примитивные типы Java и строки Java.
  4. Trie должен использовать символ $ (доллар) для обозначения конца слова. (см. изображение ниже)

введите здесь описание изображения

  1. Могу предположить, что теперь будет вставляться слово, содержащее символ $.
  2. Мне нужно реализовать Trie it в том же стиле, что и в книге.
  3. Регистр слов значения не имеет, т.е. все слова будут считаться строчными
  4. Trie должен хранить только символ конца слова и символы, применимые к слову, а не весь алфавит (как в некоторых реализациях).

Я не ожидаю, что кто-то сделает за меня реализацию (если только у них не завалялась :P). Мне просто очень нужна помощь.


person user3553706    schedule 20.04.2014    source источник
comment
Эта реализация Trie отвечает вашим потребностям, за исключением символа конца слова $. Вы должны использовать его в качестве отправной точки или ссылки. github.com/ phishman3579/java-algorithms-implementation/blob/   -  person Justin    schedule 20.04.2014
comment
@Justin Спасибо за ссылку, но, к сожалению, это не оптимально, но я мог бы использовать некоторые функции. Связанный код сохраняет только один символ за раз в каждом узле и никогда не сохраняет все слово в конечном узле. Таким образом, вместо A->AMMO ОНО делает A->M->M->O (конец слова для O = правда)   -  person user3553706    schedule 20.04.2014
comment
Ааа, я не знал, что он компактный. Взгляните на эту ссылку с того же сайта: github.com/phishman3579/java-algorithms-implementation/blob/   -  person Justin    schedule 20.04.2014


Ответы (1)


Прежде всего, я не думаю, что вам следует делать листовые узлы и внутренние узлы отдельными классами. Я рекомендую создать универсальный класс узлов с методом isLeaf(). Этот метод вернет true, если у узла нет потомков.

Вот псевдокод более высокого уровня для функций, которые вам нужно реализовать. Для простоты я предполагаю существование метода getIndex(), который возвращает индекс, соответствующий символу.

Insert(String str)
    Node current = null
    for each character in str
        int index = getIndex(character)
        if current.children[index] has not been initialized
            initialize current.children[index] to be a new Node
        current = current.children[index]

Вы можете легко расширить этот псевдокод в соответствии с вашими потребностями. Например, если вы хотите вернуть false всякий раз, когда вставка не удалась:

  • Вернуть false, если входная строка равна нулю
  • Вернуть false, если входная строка содержит недопустимые символы

А теперь немного высокоуровневого псевдокода для удаления.

Remove(String str)
    Node current = null
    for each character in str
        int index = getIndex(character)
        current = current.children[index] 

    // At this point, we found the node we want to remove. However, we want to 
    // delete as many ancestor nodes as possible. We can delete an ancestor node 
    // if it is not need it any more. That is, we can delete an ancestor node 
    // if it has exactly one child. 

    Node ancestor = current
    while ancestor is not null
        if ancestor has 2 or more children
            break out of loop 
        else if ancestor has less than 2 children
            Node grandAncestor = ancestor.parent
            if grandAncestor is not null
                reinitialize grandAncestor.children // this has the effect of removing ancestor

        ancestor = ancestor.parent   

На очень высоком уровне мы следуем входной строке к узлу, который хотим удалить. После этого мы проходим вверх по дереву по родительским указателям и удаляем каждый узел с 1 дочерним элементом (поскольку он больше не нужен). Как только мы достигнем узла с двумя дочерними элементами, мы остановимся.

Как и Insert, мы можем легко расширить этот псевдокод, чтобы он возвращал false всякий раз, когда удаление не удается:

  • Вернуть false, если входная строка равна нулю
  • Вернуть false, если входная строка содержит недопустимые символы
  • Верните false, если входная строка ведет к несуществующему узлу.

Легче всего реализовать удаление, если у вашего класса Node есть родительское поле. Однако реализовать метод без родительских точек можно, но это сложнее. Вы можете увидеть пример более сложной реализации здесь.

person Ben Sandler    schedule 07.05.2015