В информатике trie ( / ˈ t r aɪ / , / ˈ t r iː / ), также называемый цифровым деревом или префиксным деревом , [1] является типом дерева поиска : в частности, k -арной древовидной структурой данных, используемой для поиска определенных ключей из набора. Эти ключи чаще всего являются строками , со связями между узлами, определяемыми не всем ключом, а отдельными символами . Чтобы получить доступ к ключу (восстановить его значение, изменить его или удалить), trie обходит в глубину , следуя связям между узлами, которые представляют каждый символ в ключе.
В отличие от бинарного дерева поиска , узлы в trie не хранят свой связанный ключ. Вместо этого позиция узла в trie определяет ключ, с которым он связан. Это распределяет значение каждого ключа по структуре данных и означает, что не каждый узел обязательно имеет связанное значение.
Все дочерние элементы узла имеют общий префикс строки, связанной с этим родительским узлом, а корень связан с пустой строкой . Эта задача хранения данных, доступных по его префиксу, может быть выполнена оптимизированным для памяти способом с использованием радиксного дерева .
Хотя попытки могут быть зашифрованы строками символов, это не обязательно. Те же алгоритмы можно адаптировать для упорядоченных списков любого базового типа, например, перестановок цифр или форм. В частности, побитовый три-дерево зашифровано на отдельных битах, составляющих фрагмент двоичных данных фиксированной длины, например, целое число или адрес памяти . Сложность поиска ключа три-дерева остается пропорциональной размеру ключа. Специализированные реализации три-дерева, такие как сжатые попытки, используются для решения проблемы огромного дискового пространства, требуемого три-деревом в наивных реализациях.
Идея trie-дерева для представления набора строк была впервые абстрактно описана Акселем Туэ в 1912 году. [2] [3] Trie-деревья были впервые описаны в компьютерном контексте Рене де ла Брианде в 1959 году. [4] [3] [5] : 336
Идея была независимо описана в 1960 году Эдвардом Фредкиным , [6] который ввел термин trie , произнося его / ˈ t r iː / (как «дерево»), после среднего слога слова retrieval . [7] [8] Однако другие авторы произносят его / ˈ t r aɪ / (как «пытаться»), пытаясь отличить его словесно от «дерева». [7] [8] [3]
Trie — это форма индексированной по строкам структуры данных поиска, которая используется для хранения списка слов в словаре, по которому можно осуществлять поиск таким образом, чтобы обеспечить эффективную генерацию списков завершения . [9] [10] : 1 Префиксное trie — это упорядоченная древовидная структура данных, используемая для представления набора строк в конечном алфавитном наборе, что позволяет эффективно хранить слова с общими префиксами. [1]
Trie могут быть эффективными в алгоритмах поиска строк , таких как предиктивный текст , приблизительное сопоставление строк и проверка орфографии по сравнению с бинарными деревьями поиска. [11] [8] [12] : 358 Trie можно рассматривать как древовидный детерминированный конечный автомат . [13]
Попытки поддерживают различные операции: вставку, удаление и поиск строкового ключа. Попытки состоят из узлов, содержащих ссылки, которые либо указывают на другие дочерние узлы суффикса, либо на null . Как и для каждого дерева, на каждый узел, кроме корня, указывает только один другой узел, называемый его родителем . Каждый узел содержит столько ссылок, сколько символов в применимом алфавите (хотя попытки, как правило, имеют значительное количество ссылок null). В некоторых случаях используемый алфавит — это просто алфавит кодировки символов , что приводит, например, к размеру 256 в случае (беззнакового) ASCII . [14] : 732
Нулевые ссылки внутри дочерних узлов подчеркивают следующие характеристики: [14] : 734 [5] : 336
Базовый тип структуры узлов в trie-дереве выглядит следующим образом: может содержать необязательный , который связан с каждым ключом, хранящимся в последнем символе строки, или терминальным узлом.
Поиск значения в trie осуществляется на основе символов в ключе строки поиска, поскольку каждый узел в trie содержит соответствующую ссылку на каждый возможный символ в данной строке. Таким образом, следование строке в trie дает связанное значение для данного ключа строки. Нулевая ссылка во время поиска указывает на отсутствие ключа. [14] : 732-733
Следующий псевдокод реализует процедуру поиска для заданного строкового ключа в корневом trie-дереве x . [15] : 135
В приведенном выше псевдокоде x и key соответствуют указателю корневого узла trie и строке key соответственно. Операция поиска в стандартном trie занимает время, где — размер строкового параметра , а соответствует размеру алфавита . [16] : 754 Двоичные деревья поиска , с другой стороны, берут в худшем случае, поскольку поиск зависит от высоты дерева ( ) BST (в случае сбалансированных деревьев ), где и — количество ключей и длина ключей. [12] : 358
Trie занимает меньше места по сравнению с BST в случае большого количества коротких строк, поскольку узлы разделяют общие начальные подпоследовательности строк и неявно хранят ключи. [12] : 358 Конечный узел дерева содержит ненулевое значение, и поиск считается успешным, если соответствующее значение найдено в trie, и неудачным , если нет. [14] : 733
Вставка в trie осуществляется с использованием наборов символов в качестве индексов для дочернего массива до тех пор, пока не будет достигнут последний символ строкового ключа. [14] : 733-734 Каждый узел в trie соответствует одному вызову процедуры радиксной сортировки , поскольку структура trie отражает выполнение шаблона радиксной сортировки сверху вниз. [15] : 135
Если до достижения последнего символа строки key встречается нулевая ссылка, создается новый узел (строка 3). [14] : 745 Значение конечного узла присваивается входному значению; поэтому, если первое было ненулевым во время вставки, оно заменяется новым значением.
Удаление пары ключ-значение из дерева включает в себя поиск конечного узла с соответствующим строковым ключом, маркировку конечного индикатора и значения как false и null соответственно. [14] : 740
Ниже представлена рекурсивная процедура удаления строкового ключа из корневого дерева ( x ).
Процедура начинается с проверки ключа ; null обозначает прибытие конечного узла или конца строки ключа. Если узел является конечным и не имеет потомков, он удаляется из trie (строка 14). Однако конец строки ключа без конечного узла указывает на то, что ключ не существует, поэтому процедура не изменяет trie. Рекурсия продолжается путем увеличения индекса ключа .
Trie можно использовать вместо хэш-таблицы , по сравнению с которой он имеет следующие преимущества: [12] : 358
Однако попытки менее эффективны, чем хэш-таблица, когда данные напрямую доступны на вторичном устройстве хранения, таком как жесткий диск, который имеет большее время случайного доступа , чем основная память . [6] Попытки также невыгодны, когда значение ключа не может быть легко представлено в виде строки, например, числа с плавающей точкой , где возможны множественные представления (например, 1 эквивалентно 1,0, +1,0, 1,00 и т. д.), [12] : 359 однако его можно однозначно представить как двоичное число в IEEE 754 , по сравнению с форматом дополнения до двух . [17]
Попытки могут быть представлены несколькими способами, соответствующими различным компромиссам между использованием памяти и скоростью операций. [5] : 341 Использование вектора указателей для представления префикса потребляет огромное количество памяти; однако, объем памяти может быть уменьшен за счет времени выполнения, если для каждого вектора узлов используется односвязный список , поскольку большинство записей вектора содержат . [3] : 495
Такие методы, как сокращение алфавита, могут облегчить высокую сложность пространства, переосмысливая исходную строку как длинную строку в меньшем алфавите, т. е. строку из n байтов можно альтернативно рассматривать как строку из 2 n четырехбитных единиц и хранить в trie с шестнадцатью указателями на узел. Однако в худшем случае для поиска необходимо посетить вдвое больше узлов, хотя требования к пространству снижаются в восемь раз. [5] : 347–352 Другие методы включают хранение вектора из 256 указателей ASCII в виде битовой карты из 256 бит, представляющей алфавит ASCII, что значительно уменьшает размер отдельных узлов. [18]
Побитовые попытки используются для решения проблемы огромного требования к пространству для узлов trie в простых реализациях векторов указателей. Каждый символ в наборе строковых ключей представлен отдельными битами, которые используются для обхода trie по строковому ключу. Реализации для этих типов trie используют векторизованные инструкции ЦП для поиска первого установленного бита во входном ключе фиксированной длины (например, встроенная функция GCC ) . Соответственно, установленный бит используется для индексации первого элемента или дочернего узла в побитовом дереве с 32 или 64 записями. Затем поиск продолжается путем проверки каждого последующего бита в ключе. [19]__builtin_clz()
Эта процедура также является локальной в кэше и хорошо поддается параллелизации благодаря независимости регистров , а значит, производительна на процессорах с неупорядоченным выполнением . [19]
Radix tree , также известное как сжатое trie , представляет собой оптимизированный по пространству вариант trie, в котором любой узел с одним потомком объединяется со своим родителем; устранение ветвей узлов с одним потомком приводит к лучшим метрикам как в пространстве, так и во времени. [20] [21] : 452 Это работает лучше всего, когда trie остается статичным, а набор хранимых ключей очень разрежен в их пространстве представления. [22] : 3–16
Еще один подход заключается в «упаковке» trie-дерева, в котором эффективная с точки зрения пространства реализация разреженного упакованного trie-дерева применяется к автоматическому переносу , при котором потомки каждого узла могут чередоваться в памяти. [8]
Деревья Patricia являются частной реализацией сжатого бинарного trie, которая использует двоичное кодирование строковых ключей в своем представлении. [23] [15] : 140 Каждый узел в дереве Patricia содержит индекс, известный как «пропускное число», который хранит индекс ветвления узла, чтобы избежать пустых поддеревьев во время обхода. [15] : 140-141 Наивная реализация trie потребляет огромное количество памяти из-за большего количества конечных узлов, вызванного разреженным распределением ключей; деревья Patricia могут быть эффективны в таких случаях. [15] : 142 [24] : 3
Представление дерева Patricia показано справа. Каждое значение индекса, смежное с узлами, представляет собой «номер пропуска» — индекс бита, с которым должно быть решено ветвление. [24] : 3 Номер пропуска 1 в узле 0 соответствует позиции 1 в двоичном коде ASCII, где самый левый бит отличается в наборе ключей . [24] : 3-4 Номер пропуска имеет решающее значение для поиска, вставки и удаления узлов в дереве Patricia, и операция маскирования битов выполняется во время каждой итерации. [15] : 143
Структуры данных Trie обычно используются в предиктивных текстовых словарях или словарях автозаполнения , а также в алгоритмах приблизительного соответствия . [11] Trie обеспечивают более быстрый поиск, занимают меньше места, особенно когда набор содержит большое количество коротких строк, поэтому используются в проверке орфографии , приложениях для расстановки переносов и алгоритмах сопоставления самых длинных префиксов . [8] [12] : 358 Однако, если хранение слов словаря — это все, что требуется (т. е. нет необходимости хранить метаданные, связанные с каждым словом), минимальный детерминированный ациклический конечный автомат (DAFSA) или базисное дерево будут использовать меньше места для хранения, чем trie. Это связано с тем, что DAFSA и базисные деревья могут сжимать идентичные ветви из trie, которые соответствуют тем же суффиксам (или частям) разных сохраняемых слов. Строковые словари также используются в обработке естественного языка , например, для поиска лексикона текстового корпуса . [25] : 73
Лексикографическая сортировка набора строковых ключей может быть реализована путем построения trie-дерева для заданных ключей и обхода дерева в прямом порядке ; [26] это также форма радиксной сортировки . [27] Trie также являются фундаментальными структурами данных для burstsort , которая примечательна тем, что является самым быстрым алгоритмом сортировки строк по состоянию на 2007 год, [28] что достигается за счет эффективного использования кэша ЦП . [29]
Особый вид дерева, называемый деревом суффиксов , может использоваться для индексации всех суффиксов в тексте с целью выполнения быстрого полнотекстового поиска. [30]
Специализированный вид trie, называемый сжатым trie, используется в поисковых системах для хранения индексов — коллекции всех искомых слов. [31] Каждый конечный узел связан со списком URL-адресов — называемым списком вхождений — страниц, которые соответствуют ключевому слову. Trie хранится в основной памяти, тогда как вхождение хранится во внешнем хранилище, часто в больших кластерах , или индекс в памяти указывает на документы, хранящиеся во внешнем месте. [32]
Trie используются в биоинформатике , в частности, в программных приложениях для выравнивания последовательностей , таких как BLAST , которые индексируют все различные подстроки длины k (называемые k-мерами ) текста, сохраняя позиции их вхождений в сжатых базах данных последовательностей trie. [25] : 75
Сжатые варианты попыток, такие как базы данных для управления базой данных пересылки (FIB), используются для хранения префиксов IP-адресов в маршрутизаторах и мостах для поиска на основе префиксов с целью разрешения операций на основе масок в IP-маршрутизации . [25] : 75
Предварительный порядок узлов в дереве совпадает с лексикографическим порядком строк, которые они представляют, предполагая, что потомки узла упорядочены по меткам ребер.