Menu
Coddy logo textTech

Дерево префиксов (Trie)

Последнее обновление

Trie (произносится «трай»), или дерево префиксов, хранит множество строк по их символам: каждое ребро помечено символом, а путь от корня складывается в префикс. Слова с общим префиксом разделяют одни и те же узлы, поэтому «car», «card» и «care» повторно используют путь c-a-r. Нажмите «Воспроизвести» выше, чтобы увидеть, как слова вставляются по одному символу за раз, ветвясь только там, где различаются.

Поскольку поиск проходит по одному узлу на символ, поиск слова длины m занимает O(m) независимо от того, сколько слов хранит дерево. Это делает деревья префиксов идеальными для автодополнения, проверки орфографии и поиска по префиксу.

Сложность по времени и памяти

ОперацияСложностьПримечания
ВставкаO(m)m = длина слова
ПоискO(m)Один шаг на символ
Запрос префиксаO(m)Пройти до узла префикса
ПамятьO(total chars)Общие префиксы хранятся один раз

Шаг за шагом (вставка)

ШагЧто происходит
1Начните с корневого узла.
2Для каждого символа слова ищите подходящее дочернее ребро.
3Если оно есть, следуйте по нему (повторно используя общий префикс).
4Если нет, создайте новый дочерний узел для этого символа.
5После последнего символа отметьте этот узел как конец слова.

Разобранный пример

Вставка ["car", "card", "care"] в пустое дерево:

ШагСтруктураДействие
Вставить carroot → c → a → r✓Подходящих дочерних узлов нет, поэтому создайте c, a, r и отметьте r как конец слова.
Вставить cardroot → c → a → r✓ → d✓Повторно используйте существующий путь c-a-r, затем создайте один новый дочерний узел d и отметьте его как конец слова.
Вставить careroot → c → a → r✓ → {d✓, e✓}Повторно используйте c-a-r, ответвитесь от r новым дочерним узлом e и отметьте e как конец слова.
Найти careroot → c → a → r → e✓Пройдите c, a, r, e; последний узел отмечен как конец слова, значит care присутствует.
Найти caroot → c → aПуть существует, но a не отмечен как конец слова, поэтому ca — это префикс, но не сохранённое слово.

Когда использовать дерево префиксов

Использовать, когдаИзбегать, когда
Нужны запросы по префиксу или автодополнение по множеству строк.Нужен только поиск целых ключей — хеш-таблица быстрее и легче.
Многие хранимые слова имеют общие префиксы, поэтому узлы переиспользуются.Ключи длинные и редко пересекаются, тратя по узлу на каждый символ.
Нужно возвращать ключи в отсортированном порядке через обход.Память ограничена — указатели на детей у каждого узла дают значительные накладные расходы.
Стоимость поиска должна зависеть от длины ключа, а не от размера набора данных.Алфавит огромен (например, весь Unicode), а дети хранятся плотно.

Код Trie (Prefix Tree)

Чистая, готовая к запуску реализация Trie (Prefix Tree) на Python, JavaScript, Java, C++, C. Выберите язык, скопируйте код или откройте его уже загруженным в плейграунде Coddy.

Код Trie (Prefix Tree) на Python

Python
1class TrieNode:2    def __init__(self):3        self.children = {}4        self.is_word = False5
6
7class Trie:8    def __init__(self):9        self.root = TrieNode()10
11    def insert(self, word):12        node = self.root13        for ch in word:14            node = node.children.setdefault(ch, TrieNode())15        node.is_word = True16
17    def search(self, word):18        node = self._walk(word)19        return node is not None and node.is_word20
21    def starts_with(self, prefix):22        return self._walk(prefix) is not None23
24    def _walk(self, s):25        node = self.root26        for ch in s:27            if ch not in node.children:28                return None29            node = node.children[ch]30        return node31
32
33trie = Trie()34for word in ["car", "card", "care", "dog"]:35    trie.insert(word)36
37print("search(card):     ", trie.search("card"))38print("search(ca):       ", trie.search("ca"))39print("starts_with(ca):  ", trie.starts_with("ca"))40print("starts_with(do):  ", trie.starts_with("do"))41print("starts_with(cat): ", trie.starts_with("cat"))
Запустите этот код в плейграунде Python

Часто задаваемые вопросы о деревьях префиксов

Для чего используется дерево префиксов?
Деревья префиксов обеспечивают функции на основе префиксов: автодополнение и подсказки поиска, проверку орфографии, таблицы IP-маршрутизации и словарные запросы. Везде, где нужно быстро отвечать на вопрос «начинается ли какое-либо хранимое слово с этого префикса?», trie проявляет себя лучше всего.
Какова временная сложность дерева префиксов?
Вставка, поиск и запросы префикса выполняются за O(m), где m — длина слова или префикса, независимо от того, сколько слов хранится. Компромисс — память: дерево может занимать много места, хотя общие префиксы хранятся только один раз.
В чём разница между деревом префиксов и хеш-таблицей?
Хеш-таблица даёт в среднем поиск целых ключей за O(1), но не может отвечать на запросы по префиксу. Trie немного медленнее при поиске, но естественно поддерживает поиск по префиксу, упорядоченный обход и автодополнение — по этой причине его предпочитают для таких случаев.
Когда стоит использовать дерево префиксов вместо двоичного дерева поиска?
Используйте trie, когда ключи — это строки и нужны поиски по префиксу: оно работает за O(m) на операцию по длине ключа, тогда как сбалансированное BST стоит O(m log n), потому что каждое сравнение просматривает строку, а таких сравнений log n. BST — лучший выбор, когда ключи не строкового вида или когда накладные расходы на память важнее скорости работы с префиксами.
Как обрабатывается конец слова в дереве префиксов?
Каждый узел несёт флаг конца слова, который устанавливается только тогда, когда там заканчивается полностью вставленное слово. Без него нельзя отличить хранимое слово от простого префикса — например, после вставки card узел для car существует, но должен считаться словом только если car тоже было вставлено.
Всегда ли деревья префиксов экономят память за счёт разделения префиксов?
Не всегда. Разделение помогает лишь тогда, когда многие ключи пересекаются; с длинными различными ключами trie может использовать намного больше памяти, чем хеш-множество, поскольку каждый символ становится отдельным узлом с указателями на детей. Если проблема в памяти, сжатое radix-дерево объединяет цепочки с единственным ребёнком, чтобы уменьшить эти накладные расходы.
Coddy programming languages illustration

Освойте алгоритмы с Coddy

НАЧАТЬ