Qual é a diferença entre as estruturas de dados trie e radix trie?

95

As estruturas de dados trie e radix trie são a mesma coisa?

Se eles são iguais, então qual é o significado de trie radical (também conhecido como trie Patricia)?

Aryak Sengupta
fonte
4
Sou o único que acha um pouco chato que a tag seja em radix-treevez de radix-trie? Além disso, existem algumas questões marcadas com ele.
errantlinguist
@errantlinguist A Wikipedia intitula o radix trieartigo como Radix tree. Além disso, o termo "árvore Radix" é amplamente utilizado na literatura. Se qualquer coisa, chamar uma tentativa de "árvores de prefixo" faria mais sentido para mim. Afinal, são todas estruturas de dados em árvore .
Amelio Vazquez-Reina
Também: "Qual é o significado de trie radix (também conhecido como trie Patricia)?" isso assume que as árvores radix e as árvores PATRICIA são a mesma coisa, mas não são (por exemplo, veja esta resposta ). Árvores PATRICIA são árvores obtidas ao executar o algoritmo PATRICIA (também FYI PATRICIA é uma sigla, que significa "Algoritmo Prático para recuperar informações codificadas em alfanumérico"). As árvores resultantes podem ser entendidas como árvores raiz com radix = 2, o que significa que você atravessa a árvore procurando por log2(radix)=1bits da string de entrada de cada vez.
Amelio Vazquez-Reina

Respostas:

118

Uma árvore raiz é uma versão compactada de um trie. Em um trie, em cada borda você escreve uma única letra, enquanto em uma árvore PATRICIA (ou árvore raiz) você armazena palavras inteiras.

Agora, suponha que você tenha as palavras hello, hate have. Para armazená-los em um trie , seria semelhante a:

    e - l - l - o
  /
h - a - t
      \
       v - e

E você precisa de nove nós. Coloquei as letras nos nós, mas na verdade elas identificam as bordas.

Em uma árvore raiz, você terá:

            *
           /
        (ello)
         /
* - h - * -(a) - * - (t) - *
                 \
                 (ve)
                   \
                    *

e você precisa de apenas cinco nós. Na imagem acima, os nós são os asteriscos.

Portanto, no geral, uma árvore raiz ocupa menos memória , mas é mais difícil de implementar. Caso contrário, o caso de uso de ambos é praticamente o mesmo.

Ivaylo Strandjev
fonte
Obrigado ... Você pode me fornecer um bom recurso para estudar o DS ... Isso seria de grande ajuda ...
Aryak Sengupta
Acredito que a única coisa que usei quando implementei o Trie pela primeira vez foi o artigo da wikipedia . Não estou dizendo que seja perfeito, mas é bom o suficiente.
Ivaylo Strandjev
1
posso dizer que pesquisar no TRIE é mais rápido do que na árvore Radix? Porque no TRIE, se você quiser pesquisar o próximo caractere, você precisa ver o i-ésimo índice no array filho do nó atual, mas na árvore raiz você precisa pesquisar todos os nós filhos sequencialmente. Veja a implementação code.google.com/p/radixtree/source/browse/trunk/RadixTree/src/…
Tentativa em
4
Na verdade, em uma árvore raiz você não pode ter mais do que uma única aresta começando com a mesma letra, então você pode usar a mesma indexação constante.
Ivaylo Strandjev
1
@ Trying Algorithmically Radix é mais rápido que TRIE, por isso vale a pena fazer a compressão. Menos nós para carregar e menos espaço são geralmente melhores. Dito isso, a qualidade da implementação pode variar.
Glenn Teitelbaum
68

Minha pergunta é se a estrutura de dados Trie e Radix Trie são a mesma coisa?

Resumindo, não. A categoria Radix Trie descreve uma categoria particular de Trie , mas isso não significa que todas as tentativas são tentativas radix.

Se eles são [não] iguais, então qual é o significado de Radix trie (também conhecido como Patricia Trie)?

Suponho que você pretendia escrever não está em sua pergunta, daí minha correção.

Da mesma forma, PATRICIA denota um tipo específico de trie raiz, mas nem todas as tentativas raiz são tentativas PATRICIA.


O que é um trie?

"Trie" descreve uma estrutura de dados em árvore adequada para uso como um array associativo, onde ramos ou arestas correspondem a partes de uma chave. A definição de partes é um tanto vaga, aqui, porque diferentes implementações de tentativas usam diferentes comprimentos de bit para corresponder às arestas. Por exemplo, um trie binário tem duas arestas por nó que correspondem a 0 ou 1, enquanto um trie de 16 vias tem dezesseis arestas por nó que correspondem a quatro bits (ou um dígito hexadecimal: 0x0 a 0xf).

Este diagrama, recuperado da Wikipedia, parece representar um trie com (pelo menos) as chaves 'A', 'para', 'chá', 'ted', 'dez' e 'pousada' inseridas:

Teste Básico

Se este teste fosse armazenar itens para as chaves 't', 'te', 'i' ou 'in', seria necessário haver informações extras presentes em cada nó para distinguir entre nós nulos e nós com valores reais.


O que é um radix trie?

"Radix trie" parece descrever uma forma de trie que condensa partes de prefixos comuns, como Ivaylo Strandjev descreveu em sua resposta. Considere que um trie de 256 vias que indexa as teclas "smile", "sorriu", "sorri" e "sorrindo" usando as seguintes atribuições estáticas:

root['s']['m']['i']['l']['e']['\0'] = smile_item;
root['s']['m']['i']['l']['e']['d']['\0'] = smiled_item;
root['s']['m']['i']['l']['e']['s']['\0'] = smiles_item;
root['s']['m']['i']['l']['i']['n']['g']['\0'] = smiling_item;

Cada subscrito acessa um nó interno. Isso significa que para recuperar smile_item, você deve acessar sete nós. Oito acessos de nó correspondem a smiled_iteme smiles_item, e nove a smiling_item. Para esses quatro itens, há quatorze nós no total. Todos eles têm os primeiros quatro bytes (correspondentes aos primeiros quatro nós) em comum, no entanto. Ao condensar esses quatro bytes para criar um rootque corresponde a ['s']['m']['i']['l'], quatro acessos de nó foram otimizados. Isso significa menos memória e menos acessos a nós, o que é uma indicação muito boa. A otimização pode ser aplicada recursivamente para reduzir a necessidade de acessar bytes de sufixo desnecessários. Eventualmente, você chega a um ponto em que está apenas comparando as diferenças entre a chave de pesquisa e as chaves indexadas em locais indexados pelo trie. Este é um trie radical.

root = smil_dummy;
root['e'] = smile_item;
root['e']['d'] = smiled_item;
root['e']['s'] = smiles_item;
root['i'] = smiling_item;

Para recuperar itens, cada nó precisa de uma posição. Com uma chave de busca de "sorrisos" e root.position4, acessamos root["smiles"[4]], que por acaso é root['e']. Armazenamos isso em uma variável chamada current. current.positioné 5, que é o local da diferença entre "smiled"e "smiles", portanto, o próximo acesso será root["smiles"[5]]. Isso nos leva ao smiles_itemfim de nossa string. Nossa pesquisa terminou e o item foi recuperado, com apenas três acessos de nó em vez de oito.


O que é um trie PATRICIA?

Um trie PATRICIA é uma variante de tentativas radix para as quais deve haver apenas nnós usados ​​para conter nitens. No nosso trie pseudocódigo radix grosseiramente demonstrado acima, há cinco nós no total: root(que é um nó nullary, que não contém nenhum valor real), root['e'], root['e']['d'], root['e']['s']e root['i']. Em um teste PATRICIA deve haver apenas quatro. Vamos dar uma olhada em como esses prefixos podem diferir olhando para eles em binário, já que PATRICIA é um algoritmo binário.

smile:   0111 0011  0110 1101  0110 1001  0110 1100  0110 0101  0000 0000  0000 0000
smiled:  0111 0011  0110 1101  0110 1001  0110 1100  0110 0101  0110 0100  0000 0000
smiles:  0111 0011  0110 1101  0110 1001  0110 1100  0110 0101  0111 0011  0000 0000
smiling: 0111 0011  0110 1101  0110 1001  0110 1100  0110 1001  0110 1110  0110 0111 ...

Vamos considerar que os nós são adicionados na ordem em que são apresentados acima. smile_itemé a raiz desta árvore. A diferença, em negrito para torná-lo um pouco mais fácil de localizar, está no último byte de "smile", no bit 36. Até este ponto, todos os nossos nós têm o mesmo prefixo. smiled_nodepertence a smile_node[0]. A diferença entre "smiled"e "smiles"ocorre no bit 43, onde "smiles"tem um bit '1', então smiled_node[1]é smiles_node.

Ao invés de usar NULLcomo sucursais e / ou informação interna adicional para indicar quando uma pesquisa termina, os ramos ligar de volta até o lugar da árvore, então uma busca termina quando o deslocamento para teste diminui em vez de aumentar. Aqui está um diagrama simples de tal árvore (embora PATRICIA realmente seja mais um gráfico cíclico do que uma árvore, como você verá), que foi incluído no livro de Sedgewick mencionado abaixo:

Diagrama PATRICIA simples

Um algoritmo PATRICIA mais complexo envolvendo chaves de comprimento variante é possível, embora algumas das propriedades técnicas de PATRICIA sejam perdidas no processo (ou seja, que qualquer nó contém um prefixo comum com o nó anterior a ele):

Diagrama PATRICIA complexo

Ramificando assim, há uma série de benefícios: Cada nó contém um valor. Isso inclui a raiz. Como resultado, o comprimento e a complexidade do código se tornam muito mais curtos e provavelmente um pouco mais rápidos na realidade. Pelo menos um ramo e no máximo kramos (onde ké o número de bits na chave de pesquisa) são seguidos para localizar um item. Os nós são minúsculos , pois armazenam apenas duas ramificações cada, o que os torna bastante adequados para otimização de localidade do cache. Essas propriedades fazem de PATRICIA meu algoritmo favorito até agora ...

Vou encurtar essa descrição aqui, a fim de reduzir a gravidade da minha artrite iminente, mas se você quiser saber mais sobre PATRICIA, pode consultar livros como "The Art of Computer Programming, Volume 3" de Donald Knuth , ou qualquer um dos "Algoritmos em {seu-idioma-favorito}, partes 1-4" de Sedgewick.

autista
fonte
Você poderia me ajudar a entender o significado do termo "Radix"! Eu entendo como, de uma forma natural, podemos tentar transformar um TRIE em um TRIE compacto, permitindo que vários símbolos / arestas se unam em uma aresta. No entanto, não consigo discernir porque um TRIE não compactado (simplesmente um TRIE) não pode ser denominado como Radix TRIE.
KGhatak de
@ Seb - realmente apreciaria seu feedback no post stackoverflow.com/questions/40087385/… na Radix Tree. Obrigado em adv.
KGhatak de
@BuckCherry Eu adoraria poder fazer isso, mas, por favor, perceba que, como meu computador foi roubado, não seria capaz de me esforçar para uma resposta adequada.
autista de
18

TRIE:
Podemos ter um esquema de pesquisa em que, em vez de comparar uma chave de pesquisa inteira com todas as chaves existentes (como um esquema de hash), também podemos comparar cada caractere da chave de pesquisa. Seguindo essa ideia, podemos construir uma estrutura (conforme mostrado abaixo) que possui três chaves existentes - “ dad ”, “ dab ” e “ cab ”.

         [root]
     ...// | \\...
           |  \
           c   d
           |    \
          [*]    [*]
      ...//|\.  ./|\\...        Fig-I
        a       a
       /       /
     [*]      [*]
 ...//|\..  ../|\\...
    /        /   \
   B        b     d
  /        /       \
 []       []       []

(cab)   (dab)     (dad)

Esta é essencialmente uma árvore M-ária com nó interno, representado como [*] e nó folha, representado como []. Essa estrutura é chamada de trie . A decisão de ramificação em cada nó pode ser mantida igual ao número de símbolos únicos do alfabeto, digamos R. Para alfabetos ingleses minúsculos az, R = 26; para alfabetos ASCII estendidos, R = 256 e para dígitos binários / strings R = 2.

TRIE compacto:
Normalmente, um nó em um trie usa uma matriz com tamanho = R e, portanto, causa desperdício de memória quando cada nó tem menos arestas. Para contornar a preocupação com a memória, várias propostas foram feitas. Com base nessas variações, os trie também são chamados de " trie compacto " e " trie compactado ". Embora uma nomenclatura consistente seja rara, uma versão mais comum de um trie compacto é formada pelo agrupamento de todas as arestas quando os nós têm aresta única. Utilizando este conceito, o acima (Fig-I) trie com teclas de “pai”, “dab”, e “cab” pode tomar abaixo formulário.

         [root]
     ...// | \\...
           |  \
          cab  da
           |    \
          [ ]   [*]                Fig-II
               ./|\\...
                 |  \
                 b   d
                 |    \
                []    []

Observe que cada um de 'c', 'a' e 'b' é a única aresta de seu nó pai correspondente e, portanto, eles são conglomerados em uma única aresta “cab”. Da mesma forma, 'd' e a 'são mesclados em uma única aresta rotulada como “da”.

Radix Trie:
O termo radix , em matemática, significa uma base de um sistema numérico e indica essencialmente o número de símbolos únicos necessários para representar qualquer número nesse sistema. Por exemplo, o sistema decimal é a raiz dez e o sistema binário é a raiz dois. Usando o conceito semelhante, quando estamos interessados ​​em caracterizar uma estrutura de dados ou um algoritmo pelo número de símbolos únicos do sistema representacional subjacente, marcamos o conceito com o termo “raiz”. Por exemplo, “classificação básica” para determinado algoritmo de classificação. Na mesma linha de lógica, todas as variantes do triecujas características (como profundidade, necessidade de memória, tempo de execução de busca / acerto, etc.) dependem da raiz dos alfabetos subjacentes, podemos chamá-los de “trie” de raiz. Por exemplo, tanto um trie não compactado quanto um compactado quando usa alfabetos az, podemos chamá-lo de trie raiz 26 . Qualquer trie que usa apenas dois símbolos (tradicionalmente '0' e '1') pode ser chamado de trie raiz 2 . No entanto, de alguma forma, muitas literaturas restringiram o uso do termo “Radix Trie” apenas para o trie compactado .

Prelúdio à Árvore / Teste de PATRICIA:
Seria interessante notar que até mesmo strings como chaves podem ser representadas usando alfabetos binários. Se assumirmos a codificação ASCII, então uma chave “dad” pode ser escrita na forma binária, escrevendo a representação binária de cada caractere em sequência, digamos como “ 01100100 01100001 01100100 ” escrevendo as formas binárias de 'd', 'a' e 'd' sequencialmente. Usando este conceito, um trie (com Radix Two) pode ser formado. Abaixo, descrevemos esse conceito usando uma suposição simplificada de que as letras 'a', 'b', 'c' e 'd' são de um alfabeto menor em vez de ASCII.

Nota para a Fig-III: como mencionado, para facilitar a representação, vamos supor um alfabeto com apenas 4 letras {a, b, c, d} e suas representações binárias correspondentes são “00”, “01”, “10” e “11” respectivamente. Com isso, nossas teclas de string “dad”, “dab” e ”cab” tornam-se “110011”, “110001” e “100001” respectivamente. A tentativa para isso será mostrada abaixo na Fig-III (os bits são lidos da esquerda para a direita, assim como as strings são lidas da esquerda para a direita).

          [root]
             \1               
              \
              [*]
             0/ \1               
             /   \
           [*]   [*]         
           0/     /               
           /     /0
         [*]    [*]      
        0/      /               
        /      /0
      [*]    [*]
     0/     0/ \1                Fig-III
     /      /   \
    [*]   [*]   [*]
     \1     \1    \1
      \      \     \
      []     []    []
    (cab)   (dab) (dad)

PATRICIA Trie / Tree:
Se compactarmos o trie binário acima (Fig-III) usando compactação de borda única, ele teria muito menos nós do que mostrado acima e, ainda assim, os nós ainda seriam mais do que 3, o número de chaves que contém . Donald R. Morrison descobriu (em 1968) uma maneira inovadora de usar o trie binário para representar N chaves usando apenas N nós e chamou essa estrutura de dados de PATRICIA. Sua estrutura trie essencialmente se livrou de arestas únicas (ramificação unilateral); e, ao fazer isso, ele também se livrou da noção de dois tipos de nós - nós internos (que não representam nenhuma chave) e nós folha (que representam chaves). Ao contrário da lógica de compactação explicada acima, seu teste usa um conceito diferente em que cada nó inclui uma indicação de quantos bits de uma chave devem ser ignorados para tomar uma decisão de ramificação. Ainda outra característica de seu teste PATRICIA é que ele não armazena as chaves - o que significa que tal estrutura de dados não será adequada para responder a perguntas como listar todas as chaves que correspondem a um determinado prefixo , mas é bom para descobrir se existe uma chave ou não está no teste. No entanto, o termo Patricia Tree ou Patricia Trie tem, desde então, sido usado em muitos sentidos diferentes, mas semelhantes, como, para indicar um trie compacto [NIST], ou para indicar um trie de raiz com raiz dois [conforme indicado em um sutil maneira em WIKI] e assim por diante.

Trie que pode não ser uma Trie Radix: Trie de
Busca Ternária (também conhecida como Árvore de Busca Ternária) freqüentemente abreviada como TST é uma estrutura de dados (proposta por J. Bentley e R. Sedgewick ) que se parece muito com um trie com ramificação de três vias. Para tal árvore, cada nó tem um alfabeto característico 'x', de modo que a decisão de ramificação é determinada pelo fato de um caractere de uma chave ser menor, igual ou maior que 'x'. Devido a esse recurso fixo de ramificação de 3 vias, ele fornece uma alternativa eficiente em termos de memória para o trie, especialmente quando R (raiz) é muito grande, como para alfabetos Unicode. Curiosamente, o TST, ao contrário do trie (R-way) , não tem suas características influenciadas por R. Por exemplo, a falha de pesquisa do TST é ln (N)em oposição ao log R (N) para R-way Trie. Os requisitos de memória do TST, ao contrário do R-way trie é não uma função de R também. Portanto, devemos ter o cuidado de chamar um TST de radix-trie. Eu, pessoalmente, não acho que devemos chamá-lo de raiz-trie, uma vez que nenhuma (até onde eu sei) de suas características é influenciada pela raiz, R, de seus alfabetos subjacentes.

KGhatak
fonte
2
Como alguém que implementou PATRICIA de acordo com Morrison, Sedgewick e Knuth, posso dizer que o algoritmo que você descreveu aqui (que também tentei descrever em minha resposta) ainda é muito adequado para responder a perguntas como listar todas as chaves que correspondem a um determinado prefixo . PS Ótimo ver alguém na bola com relação a: aquela outra pergunta :) Eu gosto dessa explicação.
autista em
Re "não será adequado para responder perguntas como listar todas as chaves que correspondem a um determinado prefixo", sério?
Pacerier
@Pacerier Certo! O PATRICIA clássico armazena um inteiro, que você pode usar como índice para um array. No array você coloca a string. No teste, você coloca o índice de array baseado em 0 para a string. Faça com que as funções de pesquisa, comparação e extração de bits operem sobre a string correspondente ao inteiro em vez do inteiro, e se sua função de inserção for baseada nas outras (como deveria ser, já que há muita lógica repetida lá) e você ' Estarei bem no seu caminho. Você também pode usar uintptr_tcomo seu inteiro , uma vez que esse tipo parece ser normalmente esperado (embora não obrigatório).
autista
Você afirma que "muitas literaturas restringiam o uso do termo“ Radix Trie ”apenas para o trie compactado.". Na verdade, não consigo encontrar nenhuma outra referência além da wikipedia. Você encontrou algum outro?
wds
@ wds - Você pode estar certo, pois eu realmente não me lembro quais são os recursos que me referi quando escrevi isto. Uma pesquisa rápida no Google me dá links como mathcs.emory.edu/~cheung/Courses/323/Syllabus/Text/trie02.html ou tutorialsdiary.com/radix-trie-patricia-trie-or-compressed-trie que essencialmente apontam para ou (muito provavelmente) derivado / influenciado por wiki. Se eu encontrar qualquer outro recurso confiável / acadêmico, postarei aqui.
KGhatak